commit 82e84c33c4194a49d8696ea083f300e40ae43598 Author: Patrick Date: Sun Apr 7 14:49:51 2019 +0200 first commit diff --git a/.project b/.project new file mode 100644 index 0000000..16865b4 --- /dev/null +++ b/.project @@ -0,0 +1,24 @@ + + + Openhab2 + + + + + + json.validation.builder + + + + + org.tizen.web.project.builder.WebBuilder + + + + + + json.validation.nature + org.eclipse.wst.jsdt.core.jsNature + org.tizen.web.project.builder.WebNature + + diff --git a/.settings/.jsdtscope b/.settings/.jsdtscope new file mode 100644 index 0000000..c487c06 --- /dev/null +++ b/.settings/.jsdtscope @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/.settings/org.eclipse.wst.css.core.prefs b/.settings/org.eclipse.wst.css.core.prefs new file mode 100644 index 0000000..5ddc6bd --- /dev/null +++ b/.settings/org.eclipse.wst.css.core.prefs @@ -0,0 +1,2 @@ +css-profile/=org.eclipse.wst.css.core.cssprofile.css3 +eclipse.preferences.version=1 diff --git a/.settings/org.eclipse.wst.jsdt.ui.superType.container b/.settings/org.eclipse.wst.jsdt.ui.superType.container new file mode 100644 index 0000000..3bd5d0a --- /dev/null +++ b/.settings/org.eclipse.wst.jsdt.ui.superType.container @@ -0,0 +1 @@ +org.eclipse.wst.jsdt.launching.baseBrowserLibrary \ No newline at end of file diff --git a/.settings/org.eclipse.wst.jsdt.ui.superType.name b/.settings/org.eclipse.wst.jsdt.ui.superType.name new file mode 100644 index 0000000..05bd71b --- /dev/null +++ b/.settings/org.eclipse.wst.jsdt.ui.superType.name @@ -0,0 +1 @@ +Window \ No newline at end of file diff --git a/.sign/.manifest.tmp b/.sign/.manifest.tmp new file mode 100644 index 0000000..a3a53e7 --- /dev/null +++ b/.sign/.manifest.tmp @@ -0,0 +1,340 @@ +config.xml__DEL__GxzJ0cd5vZqiKf7ncDFwwfV6IMiUf/4dVt2aKMI0ZR0= +css/style.css__DEL__zZAIh+P4GlT4d5svP2PxBGKqK1UNw13Oma2g1+aPQfg= +icon.png__DEL__vPjxjOppORd6hn9Bw4sh06gqtDoJzoFbV/8e9FyIdvk= +index.html__DEL__zhJg3MyxJjPq3+GmqMVGlGPGyiv0vf09mhSP/tTjNGQ= +js/app.js__DEL__Sqm38++Ho3FHKe6iDgIJiIvxquD2zmSHhSaVXubSd7Q= +js/circle-helper.js__DEL__0+TbSqg2TfiaBBu+OpgUziLxj8xqrvGgo5rR4vZRB9U= +js/lowBatteryCheck.js__DEL__+i5daekAbnIThj3rFOl0UxpMhcCvNBzIUCR5tkh0Flg= +lib/tau/LICENSE.Flora__DEL__R4988xI3tkQMAcLGLOROY5Wp8YTDrI0X90uMvrydtQw= +lib/tau/VERSION__DEL__0ukTrmj+rXT674x9SPLCs6ezcKZxSRyjZoVvWGQpi9U= +lib/tau/wearable/js/tau.js__DEL__yKlkTLe4BKz6si0fZBr4fD7FoL4zInZkUu183ZDOcdo= +lib/tau/wearable/js/tau.min.js__DEL__yJz2NgNxUqtqEZfUVD+KGHVE2iL5tccsaEap7PbHqio= +lib/tau/wearable/theme/blue/images/Actionbar/b_more_option.png__DEL__kc1wJhzruwjgLS1vfPkmSQHz67R48K6bh8LpwjDuE+M= +lib/tau/wearable/theme/blue/images/Actionbar/tw_ic_menu_moreoverflow_holo_dark.png__DEL__qkbuZm3KXZNobT9lxfhl6LdiG8ERnIdYMArm45mDI3g= +lib/tau/wearable/theme/blue/images/Bottom_button/tw_bottom_btn_bg.png__DEL__DMHAhBUsbboDCd5y8Q83zu3fnJyfF1QmmQzvP7Fjf1U= +lib/tau/wearable/theme/blue/images/Common/b_more_option_btn_next_page.png__DEL__al6tluFLVe5845VesK7QLEaHmZygqysNWzZOilZUne4= +lib/tau/wearable/theme/blue/images/Common/b_more_option_btn_previous_page.png__DEL__xArlQu0AmI/ocXV+KTaJc6gg08S6LvGW+qjIpPRXCOg= +lib/tau/wearable/theme/blue/images/Common/b_rotary_selector_center_pointer.png__DEL__et3cgWnyqfSwP3HokpLXqZug3lwDT7lNt3p0JCrxuBE= +lib/tau/wearable/theme/blue/images/Common/masking_bg.png__DEL__xg0blL/qm/PlWrv4gh9D5VbFduHT4mEgGpNRugTHpbo= +lib/tau/wearable/theme/blue/images/Common/tw_no_item_bg.png__DEL__e4K/fWxgP0RPYt1CcPLMU+b0DwOS4fxsN+NcVcOmPKc= +lib/tau/wearable/theme/blue/images/Controller_icon/Check/ref_btn_check_holo_dark.png__DEL__NWtT1Dj4doBFgGMhL/7RjdD1FG12IcjqwujPGK1PAmY= +lib/tau/wearable/theme/blue/images/Controller_icon/Check/tw_btn_check_bg_holo_dark.png__DEL__Q0GXfa+7G5Om0sog4aOZZWvd/1ahRg4KyRF6UM2Fnb4= +lib/tau/wearable/theme/blue/images/Controller_icon/Check/tw_btn_check_holo_dark.png__DEL__0YYITXDSonw21JyxtiJjBf0qFaE0xtQ3RNVNF4qu0rk= +lib/tau/wearable/theme/blue/images/Controller_icon/On_Off/tw_btn_control_bg_holo_dark.png__DEL__Q0GXfa+7G5Om0sog4aOZZWvd/1ahRg4KyRF6UM2Fnb4= +lib/tau/wearable/theme/blue/images/Controller_icon/On_Off/tw_btn_control_off_holo_dark.png__DEL__N06Ed3Ihxxo2MldtVQKRq0qYl1eRbuo0zEe2medE1J0= +lib/tau/wearable/theme/blue/images/Controller_icon/On_Off/tw_btn_control_on_holo_dark.png__DEL__ph0ljcHGNXlJO1dPAiAQgDpRH3+mbMZZ3Ega7lNW/cw= +lib/tau/wearable/theme/blue/images/Controller_icon/Radio/tw_btn_radio_holo_dark.png__DEL__3i13zlE5UUPepY9sbRmsu3oxYmIBCpM9hwL0YyiVaEI= +lib/tau/wearable/theme/blue/images/Controller_icon/toggle_changeable.png__DEL__BzK0UNVeSZCohwIBfIkqCru85HE/86q/2MZUEQJWcoU= +lib/tau/wearable/theme/blue/images/Controller_icon/tw_btn_check_holo_dark.png__DEL__qFC8fOl6Tg7LrLrT62RSQLKSr3GP6O2UwjnG9s7DAck= +lib/tau/wearable/theme/blue/images/Controller_icon/tw_btn_checkbox_holo_dark.png__DEL__ddihq2E6gV6+g48CtlRBvDFnKx97KWUJxoNr8SQ7sUQ= +lib/tau/wearable/theme/blue/images/Controller_icon/tw_btn_checkbox_line_holo_dark.png__DEL__elPeRLEot+b5h79G5sudnUNkc6GhAyye82uL2cn9xxc= +lib/tau/wearable/theme/blue/images/Controller_icon/tw_btn_radio_holo_dark.png__DEL__SYKDGM5OUHfYf65YinTD2vzNNMNUjKEABJaxy/68hv4= +lib/tau/wearable/theme/blue/images/Controller_icon/tw_btn_radiobox_holo_dark.png__DEL__nwK+t+eTDiAjgXRMhNpbO52ccNtX1T8to7iUEfvtEw0= +lib/tau/wearable/theme/blue/images/Controller_icon/tw_btn_radiobox_line.png__DEL__JXfJFsytb7Ca9bHn2mZ7J5UVde6gr5WIQWFARUEbSAI= +lib/tau/wearable/theme/blue/images/Grid/tw_basic_grid_horizontal.png__DEL__Wv4xA+PTooZ5isNMnSm/xQCDNSoMc7W/rUG6m60NEFE= +lib/tau/wearable/theme/blue/images/Grid/tw_basic_grid_vertical.png__DEL__GE8gsheB4mDn5m7Ef0c0pXefYNoFeybCN6X+IvpJmns= +lib/tau/wearable/theme/blue/images/Indicator/b_fast_scroll_rollover_bg.png__DEL__BdM1nG2OXAGZ/Jjerne40hr4z+CUVQDaGGDC9NKHbJ4= +lib/tau/wearable/theme/blue/images/Indicator/b_index_scroll_bg.png__DEL__G1lZylmCqAW+9pxtAqkRcvWmAaqrhmKmFNQ6S+Gfofo= +lib/tau/wearable/theme/blue/images/Indicator/indicator_horizontal_dot.png__DEL__EmiQ9zOZtFKGu1XIfrFwbGkJApV30yvGAKx9MLuRjdc= +lib/tau/wearable/theme/blue/images/Indicator/indicator_horizontal_focus_dot.png__DEL__qNnHgB7/8DElpHmMab0YAkGsf15SmvWO52AwSD4brog= +lib/tau/wearable/theme/blue/images/Indicator/list_scroll_triggle.png__DEL__7GQe3ejeCKtt6U+41MqwJ8q3SaU7cwmfF1sFnFygjCw= +lib/tau/wearable/theme/blue/images/List/b_body_edge_shadow.png__DEL__xUE9SahpNHNokTSvhfV04BMB0Mdz4wsHy22HYi+93B4= +lib/tau/wearable/theme/blue/images/List/b_list_focus_bg.png__DEL__50M0LcqxGYrsu0BeMq13DAptfhO6NKmsqRFCnAxFajs= +lib/tau/wearable/theme/blue/images/List/b_list_focus_bg_108.png__DEL__Ds+e1ERJ7fKikfUF7LTSOFWaGS08JNA/R1xFTPfyGxY= +lib/tau/wearable/theme/blue/images/List/tw_list_divider.png__DEL__1k/BcizBicjmBjDJEJiy84ZPGZRaIAD1I3IHXAm1igo= +lib/tau/wearable/theme/blue/images/List/tw_list_sub_line_left.png__DEL__3zbo3CppqPwVpA3f2f9lDsBs06dPHIwlyuqx6pOxOyE= +lib/tau/wearable/theme/blue/images/List/tw_list_sub_line_right.png__DEL__hc3VpxyhSp3r9EwGpe+A4bbpaOdj9GUyBOPdj80hGTE= +lib/tau/wearable/theme/blue/images/Popup/b_popup_edge_inner_glow.png__DEL__47LZQG7H45gvdxyu7YNh84m0mqz+lHzw1v3C4y1HKNQ= +lib/tau/wearable/theme/blue/images/Popup/b_popup_edge_shadow.png__DEL__spXDbatBHTzSFR4N03w93hLrVybuw/XRjZAfneskshI= +lib/tau/wearable/theme/blue/images/Popup/toast_check.png__DEL__l7tDb2GpWcNj93JU9VgQbbDgqEtDA7bdufKK12uMkPs= +lib/tau/wearable/theme/blue/images/Popup/tw_ic_popup_left_btn_bg.png__DEL__qoH8usHbcfbB5f0cjjdPuVBueHof14iBqeXKMIQEMRk= +lib/tau/wearable/theme/blue/images/Popup/tw_ic_popup_right_btn_bg.png__DEL__VFg8qmBikNCdPflDeephVt91R7aPsIIXP9Ra6vjB89w= +lib/tau/wearable/theme/blue/images/Popup/tw_popup_bg.png__DEL__TphpTsohWyWK9j+2DXpOyvJSydRMq2Av5B0otjA9f9I= +lib/tau/wearable/theme/blue/images/Popup/tw_popup_bg_cover.png__DEL__vqXi7pK9urSnMFrdVlcc8IFvLcUduAMNIIgbnFvQc4A= +lib/tau/wearable/theme/blue/images/Popup/tw_toast_popup_bg.png__DEL__HQr3u9+djLy7CqBURR3qMnfvWxQRk+7KsyluA9KadzQ= +lib/tau/wearable/theme/blue/images/Popup/tw_toast_popup_bg_cover.png__DEL__PTz1Q9tb2jmV/h/NF704nw/+wpelkdig4FdwBoTkXIs= +lib/tau/wearable/theme/blue/images/Popup/tw_toast_popup_image_bg.png__DEL__3mjrv/wd1wI4zGsbemLKWEA3ARYZyZF/lx8XLE87aSQ= +lib/tau/wearable/theme/blue/images/Popup/tw_toast_popup_outline.png__DEL__mmYqlg9ixS5F00DUDkRiD8LJARxEPw7FwZpsINr4mwU= +lib/tau/wearable/theme/blue/images/Processing/processing_bar.png__DEL__h2ItAbWbIgB1+7uU3+RtKHSa7G1cCnNqLu+ENHLGUKQ= +lib/tau/wearable/theme/blue/images/Processing/processingfull.png__DEL__inphL/shFiOknwr0eLnUc7CGLICqacQ36GidvhaEuJ4= +lib/tau/wearable/theme/blue/images/Processing/processingsmall.png__DEL__C+hHn6OO+njwAy8GimgFpo+Owigwarpd0+/LdckPdjE= +lib/tau/wearable/theme/blue/images/Scroller/bouncing_bottom_edge.png__DEL__EwF0JIrEoFCTVRThWK/aNHTfqUzl+oQ5xaoEVeCKakQ= +lib/tau/wearable/theme/blue/images/Scroller/bouncing_bottom_glow.png__DEL__LBBYbeRMyt+BmcaK6OFcYxXCHatfcBy3WZmMEyeD494= +lib/tau/wearable/theme/blue/images/Scroller/bouncing_circle_bottom_edge.png__DEL__LrlCUI8SKgQ/Ec+nZhhCBp5rUaVN4zQUfmLeISzlj5Y= +lib/tau/wearable/theme/blue/images/Scroller/bouncing_circle_bottom_glow.png__DEL__7wNkYofR/ltKHZS5M5XO6KRfaOFY/RLoRk/60QGhqpg= +lib/tau/wearable/theme/blue/images/Scroller/bouncing_circle_left_edge.png__DEL__qJNgvYmC92FBcdgXqV7u0TCywBwAoRSW9VsYpluHc2M= +lib/tau/wearable/theme/blue/images/Scroller/bouncing_circle_left_glow.png__DEL__OC1v8JI2m0cBR8OfzterqamyGoAjGLRsxpOkjauhnP4= +lib/tau/wearable/theme/blue/images/Scroller/bouncing_circle_right_edge.png__DEL__eTu9vGNI+Pn8KBq8Tf4VPNaGJ7srNIKI/Mf/WUAUFos= +lib/tau/wearable/theme/blue/images/Scroller/bouncing_circle_right_glow.png__DEL__IvH2+9a4Np0dtDtyI/eOLn9bT/MYuzmdNjWTs2GQOTM= +lib/tau/wearable/theme/blue/images/Scroller/bouncing_circle_top_edge.png__DEL__+s7/n9aNRdSSP5C9yjgGW0DK5+WAZj1GZzaT8FtZRWc= +lib/tau/wearable/theme/blue/images/Scroller/bouncing_circle_top_glow.png__DEL__GGKaRP+Ko/HHOQHZbsp7JinOllLn/fWiWciLpxCTzVA= +lib/tau/wearable/theme/blue/images/Scroller/bouncing_left_edge.png__DEL__ezbad2I+Qvqq+HBtFj5fCR73B7u1OpaGp8LbvJiksx8= +lib/tau/wearable/theme/blue/images/Scroller/bouncing_left_glow.png__DEL__i6payEnlv8vWHucju//UBssYNzmr3SES3nSuWDuJU6c= +lib/tau/wearable/theme/blue/images/Scroller/bouncing_right_edge.png__DEL__wIF0gTMZq7piBZ9E/+l5UgyW9pSywlBedaETJbK5euE= +lib/tau/wearable/theme/blue/images/Scroller/bouncing_right_glow.png__DEL__MZ/WXjPf0eoYefs4grYwm+dI4ehPs1D8ElGWv+wLcGk= +lib/tau/wearable/theme/blue/images/Scroller/bouncing_top_edge.png__DEL__iEpyiqHYkrP3GQpul3S6C87yG+PyVBWzhrmvxzPf//0= +lib/tau/wearable/theme/blue/images/Scroller/bouncing_top_glow.png__DEL__JmKqm+7nSe0dUkYRduPMoyyfc+Rg9gTVg2J7/pJq5F4= +lib/tau/wearable/theme/blue/images/Selector/b_rotary_selector_edit_mode_ic_add.png__DEL__0uNZLiaoKNgqjrh+2LAZIq6GUFG3lGJHf9PXHbmMeM0= +lib/tau/wearable/theme/blue/images/Selector/b_rotary_selector_edit_mode_ic_delete.png__DEL__vo610xtMs+DvFmvUgCyleSB4MGxZ3pM/RWtx8uxTSg0= +lib/tau/wearable/theme/blue/images/Selector/b_rotary_selector_edit_mode_ic_delete_bg.png__DEL__BKVmFH1pFROxq/f6oH1cJ9PJeaxLGoKNF9DzThgzoCU= +lib/tau/wearable/theme/blue/images/Selector/b_rotary_selector_edit_mode_ic_delete_bg_ef.png__DEL__fvhUuEK9zAM1W8f0Z3gXWnMZO03A8Ae4CxvDcriiJhs= +lib/tau/wearable/theme/blue/images/Slider/b_ic_press_full_circle_bg.png__DEL__V1Ke5HdTcgx64WAnHV4oMLzOMyVITvvc9cTLNAL3jPY= +lib/tau/wearable/theme/blue/images/Slider/b_slider_icon_minus.png__DEL__2QJRn/uDj8ZrhFi6DGeIxKFFVluoZRr7MHAdqKePB74= +lib/tau/wearable/theme/blue/images/Slider/b_slider_icon_plus.png__DEL__4YF4AebfNXlSv1SNQGuuonzGh/f0LUj5e1DZfIWPKZk= +lib/tau/wearable/theme/blue/images/Swipelist/b_logs_icon_actionbar_btn_msg_nor.png__DEL__f6LUaSBsZsugcSX2A3xcaHs+rghK2nFAneqNp7WbOMw= +lib/tau/wearable/theme/blue/images/Swipelist/b_logs_icon_body_btn_call_nor.png__DEL__fl7TG+5Mgf/y6KR8TdSEPcC/teaTrmRLm+lr9TSpOyg= +lib/tau/wearable/theme/blue/images/TextInput/b_list_edit_field_bg.png__DEL__AK4h31Wy4eet4Z4+uT8uMy2Kgcyauiy2tvNj/MjQGKA= +lib/tau/wearable/theme/blue/images/TimePicker/time_picker_bg.png__DEL__pQDmuQlk3a/Nm4n5cfrpBSNmVVZNF0Ap+Mn7WztAzV8= +lib/tau/wearable/theme/blue/images/Title_bar/b_detail_bg_cover.png__DEL__6MewTgCyy7oMiEvq3KkLoNcPwf7TXOAoEtF14XSlYoM= +lib/tau/wearable/theme/blue/tau.css__DEL__Mnd6d8RqbVFwyKwNLKiziXqdDKtay+wmEj79r2jWL+I= +lib/tau/wearable/theme/blue/tau.min.css__DEL__B9TJ5UITKG2e98hvLldn0ns2ERy/JXEX3lgNB2VpWsM= +lib/tau/wearable/theme/brown/images/Actionbar/b_more_option.png__DEL__kc1wJhzruwjgLS1vfPkmSQHz67R48K6bh8LpwjDuE+M= +lib/tau/wearable/theme/brown/images/Actionbar/tw_ic_menu_moreoverflow_holo_dark.png__DEL__qkbuZm3KXZNobT9lxfhl6LdiG8ERnIdYMArm45mDI3g= +lib/tau/wearable/theme/brown/images/Bottom_button/tw_bottom_btn_bg.png__DEL__DMHAhBUsbboDCd5y8Q83zu3fnJyfF1QmmQzvP7Fjf1U= +lib/tau/wearable/theme/brown/images/Common/b_more_option_btn_next_page.png__DEL__al6tluFLVe5845VesK7QLEaHmZygqysNWzZOilZUne4= +lib/tau/wearable/theme/brown/images/Common/b_more_option_btn_previous_page.png__DEL__xArlQu0AmI/ocXV+KTaJc6gg08S6LvGW+qjIpPRXCOg= +lib/tau/wearable/theme/brown/images/Common/b_rotary_selector_center_pointer.png__DEL__et3cgWnyqfSwP3HokpLXqZug3lwDT7lNt3p0JCrxuBE= +lib/tau/wearable/theme/brown/images/Common/masking_bg.png__DEL__xg0blL/qm/PlWrv4gh9D5VbFduHT4mEgGpNRugTHpbo= +lib/tau/wearable/theme/brown/images/Common/tw_no_item_bg.png__DEL__e4K/fWxgP0RPYt1CcPLMU+b0DwOS4fxsN+NcVcOmPKc= +lib/tau/wearable/theme/brown/images/Controller_icon/Check/ref_btn_check_holo_dark.png__DEL__NWtT1Dj4doBFgGMhL/7RjdD1FG12IcjqwujPGK1PAmY= +lib/tau/wearable/theme/brown/images/Controller_icon/Check/tw_btn_check_bg_holo_dark.png__DEL__Q0GXfa+7G5Om0sog4aOZZWvd/1ahRg4KyRF6UM2Fnb4= +lib/tau/wearable/theme/brown/images/Controller_icon/Check/tw_btn_check_holo_dark.png__DEL__0YYITXDSonw21JyxtiJjBf0qFaE0xtQ3RNVNF4qu0rk= +lib/tau/wearable/theme/brown/images/Controller_icon/On_Off/tw_btn_control_bg_holo_dark.png__DEL__Q0GXfa+7G5Om0sog4aOZZWvd/1ahRg4KyRF6UM2Fnb4= +lib/tau/wearable/theme/brown/images/Controller_icon/On_Off/tw_btn_control_off_holo_dark.png__DEL__N06Ed3Ihxxo2MldtVQKRq0qYl1eRbuo0zEe2medE1J0= +lib/tau/wearable/theme/brown/images/Controller_icon/On_Off/tw_btn_control_on_holo_dark.png__DEL__ph0ljcHGNXlJO1dPAiAQgDpRH3+mbMZZ3Ega7lNW/cw= +lib/tau/wearable/theme/brown/images/Controller_icon/Radio/tw_btn_radio_holo_dark.png__DEL__3i13zlE5UUPepY9sbRmsu3oxYmIBCpM9hwL0YyiVaEI= +lib/tau/wearable/theme/brown/images/Controller_icon/toggle_changeable.png__DEL__BzK0UNVeSZCohwIBfIkqCru85HE/86q/2MZUEQJWcoU= +lib/tau/wearable/theme/brown/images/Controller_icon/tw_btn_check_holo_dark.png__DEL__qFC8fOl6Tg7LrLrT62RSQLKSr3GP6O2UwjnG9s7DAck= +lib/tau/wearable/theme/brown/images/Controller_icon/tw_btn_checkbox_holo_dark.png__DEL__ddihq2E6gV6+g48CtlRBvDFnKx97KWUJxoNr8SQ7sUQ= +lib/tau/wearable/theme/brown/images/Controller_icon/tw_btn_checkbox_line_holo_dark.png__DEL__elPeRLEot+b5h79G5sudnUNkc6GhAyye82uL2cn9xxc= +lib/tau/wearable/theme/brown/images/Controller_icon/tw_btn_radio_holo_dark.png__DEL__SYKDGM5OUHfYf65YinTD2vzNNMNUjKEABJaxy/68hv4= +lib/tau/wearable/theme/brown/images/Controller_icon/tw_btn_radiobox_holo_dark.png__DEL__nwK+t+eTDiAjgXRMhNpbO52ccNtX1T8to7iUEfvtEw0= +lib/tau/wearable/theme/brown/images/Controller_icon/tw_btn_radiobox_line.png__DEL__JXfJFsytb7Ca9bHn2mZ7J5UVde6gr5WIQWFARUEbSAI= +lib/tau/wearable/theme/brown/images/Grid/tw_basic_grid_horizontal.png__DEL__Wv4xA+PTooZ5isNMnSm/xQCDNSoMc7W/rUG6m60NEFE= +lib/tau/wearable/theme/brown/images/Grid/tw_basic_grid_vertical.png__DEL__GE8gsheB4mDn5m7Ef0c0pXefYNoFeybCN6X+IvpJmns= +lib/tau/wearable/theme/brown/images/Indicator/b_fast_scroll_rollover_bg.png__DEL__BdM1nG2OXAGZ/Jjerne40hr4z+CUVQDaGGDC9NKHbJ4= +lib/tau/wearable/theme/brown/images/Indicator/b_index_scroll_bg.png__DEL__G1lZylmCqAW+9pxtAqkRcvWmAaqrhmKmFNQ6S+Gfofo= +lib/tau/wearable/theme/brown/images/Indicator/indicator_horizontal_dot.png__DEL__EmiQ9zOZtFKGu1XIfrFwbGkJApV30yvGAKx9MLuRjdc= +lib/tau/wearable/theme/brown/images/Indicator/indicator_horizontal_focus_dot.png__DEL__qNnHgB7/8DElpHmMab0YAkGsf15SmvWO52AwSD4brog= +lib/tau/wearable/theme/brown/images/Indicator/list_scroll_triggle.png__DEL__7GQe3ejeCKtt6U+41MqwJ8q3SaU7cwmfF1sFnFygjCw= +lib/tau/wearable/theme/brown/images/List/b_body_edge_shadow.png__DEL__xUE9SahpNHNokTSvhfV04BMB0Mdz4wsHy22HYi+93B4= +lib/tau/wearable/theme/brown/images/List/b_list_focus_bg.png__DEL__50M0LcqxGYrsu0BeMq13DAptfhO6NKmsqRFCnAxFajs= +lib/tau/wearable/theme/brown/images/List/b_list_focus_bg_108.png__DEL__Ds+e1ERJ7fKikfUF7LTSOFWaGS08JNA/R1xFTPfyGxY= +lib/tau/wearable/theme/brown/images/List/tw_list_divider.png__DEL__1k/BcizBicjmBjDJEJiy84ZPGZRaIAD1I3IHXAm1igo= +lib/tau/wearable/theme/brown/images/List/tw_list_sub_line_left.png__DEL__3zbo3CppqPwVpA3f2f9lDsBs06dPHIwlyuqx6pOxOyE= +lib/tau/wearable/theme/brown/images/List/tw_list_sub_line_right.png__DEL__hc3VpxyhSp3r9EwGpe+A4bbpaOdj9GUyBOPdj80hGTE= +lib/tau/wearable/theme/brown/images/Popup/b_popup_edge_inner_glow.png__DEL__47LZQG7H45gvdxyu7YNh84m0mqz+lHzw1v3C4y1HKNQ= +lib/tau/wearable/theme/brown/images/Popup/b_popup_edge_shadow.png__DEL__spXDbatBHTzSFR4N03w93hLrVybuw/XRjZAfneskshI= +lib/tau/wearable/theme/brown/images/Popup/toast_check.png__DEL__l7tDb2GpWcNj93JU9VgQbbDgqEtDA7bdufKK12uMkPs= +lib/tau/wearable/theme/brown/images/Popup/tw_ic_popup_left_btn_bg.png__DEL__qoH8usHbcfbB5f0cjjdPuVBueHof14iBqeXKMIQEMRk= +lib/tau/wearable/theme/brown/images/Popup/tw_ic_popup_right_btn_bg.png__DEL__VFg8qmBikNCdPflDeephVt91R7aPsIIXP9Ra6vjB89w= +lib/tau/wearable/theme/brown/images/Popup/tw_popup_bg.png__DEL__TphpTsohWyWK9j+2DXpOyvJSydRMq2Av5B0otjA9f9I= +lib/tau/wearable/theme/brown/images/Popup/tw_popup_bg_cover.png__DEL__vqXi7pK9urSnMFrdVlcc8IFvLcUduAMNIIgbnFvQc4A= +lib/tau/wearable/theme/brown/images/Popup/tw_toast_popup_bg.png__DEL__HQr3u9+djLy7CqBURR3qMnfvWxQRk+7KsyluA9KadzQ= +lib/tau/wearable/theme/brown/images/Popup/tw_toast_popup_bg_cover.png__DEL__PTz1Q9tb2jmV/h/NF704nw/+wpelkdig4FdwBoTkXIs= +lib/tau/wearable/theme/brown/images/Popup/tw_toast_popup_image_bg.png__DEL__3mjrv/wd1wI4zGsbemLKWEA3ARYZyZF/lx8XLE87aSQ= +lib/tau/wearable/theme/brown/images/Popup/tw_toast_popup_outline.png__DEL__mmYqlg9ixS5F00DUDkRiD8LJARxEPw7FwZpsINr4mwU= +lib/tau/wearable/theme/brown/images/Processing/processing_bar.png__DEL__h2ItAbWbIgB1+7uU3+RtKHSa7G1cCnNqLu+ENHLGUKQ= +lib/tau/wearable/theme/brown/images/Processing/processingfull.png__DEL__inphL/shFiOknwr0eLnUc7CGLICqacQ36GidvhaEuJ4= +lib/tau/wearable/theme/brown/images/Processing/processingsmall.png__DEL__C+hHn6OO+njwAy8GimgFpo+Owigwarpd0+/LdckPdjE= +lib/tau/wearable/theme/brown/images/Scroller/bouncing_bottom_edge.png__DEL__EwF0JIrEoFCTVRThWK/aNHTfqUzl+oQ5xaoEVeCKakQ= +lib/tau/wearable/theme/brown/images/Scroller/bouncing_bottom_glow.png__DEL__LBBYbeRMyt+BmcaK6OFcYxXCHatfcBy3WZmMEyeD494= +lib/tau/wearable/theme/brown/images/Scroller/bouncing_circle_bottom_edge.png__DEL__LrlCUI8SKgQ/Ec+nZhhCBp5rUaVN4zQUfmLeISzlj5Y= +lib/tau/wearable/theme/brown/images/Scroller/bouncing_circle_bottom_glow.png__DEL__7wNkYofR/ltKHZS5M5XO6KRfaOFY/RLoRk/60QGhqpg= +lib/tau/wearable/theme/brown/images/Scroller/bouncing_circle_left_edge.png__DEL__qJNgvYmC92FBcdgXqV7u0TCywBwAoRSW9VsYpluHc2M= +lib/tau/wearable/theme/brown/images/Scroller/bouncing_circle_left_glow.png__DEL__OC1v8JI2m0cBR8OfzterqamyGoAjGLRsxpOkjauhnP4= +lib/tau/wearable/theme/brown/images/Scroller/bouncing_circle_right_edge.png__DEL__eTu9vGNI+Pn8KBq8Tf4VPNaGJ7srNIKI/Mf/WUAUFos= +lib/tau/wearable/theme/brown/images/Scroller/bouncing_circle_right_glow.png__DEL__IvH2+9a4Np0dtDtyI/eOLn9bT/MYuzmdNjWTs2GQOTM= +lib/tau/wearable/theme/brown/images/Scroller/bouncing_circle_top_edge.png__DEL__+s7/n9aNRdSSP5C9yjgGW0DK5+WAZj1GZzaT8FtZRWc= +lib/tau/wearable/theme/brown/images/Scroller/bouncing_circle_top_glow.png__DEL__GGKaRP+Ko/HHOQHZbsp7JinOllLn/fWiWciLpxCTzVA= +lib/tau/wearable/theme/brown/images/Scroller/bouncing_left_edge.png__DEL__ezbad2I+Qvqq+HBtFj5fCR73B7u1OpaGp8LbvJiksx8= +lib/tau/wearable/theme/brown/images/Scroller/bouncing_left_glow.png__DEL__i6payEnlv8vWHucju//UBssYNzmr3SES3nSuWDuJU6c= +lib/tau/wearable/theme/brown/images/Scroller/bouncing_right_edge.png__DEL__wIF0gTMZq7piBZ9E/+l5UgyW9pSywlBedaETJbK5euE= +lib/tau/wearable/theme/brown/images/Scroller/bouncing_right_glow.png__DEL__MZ/WXjPf0eoYefs4grYwm+dI4ehPs1D8ElGWv+wLcGk= +lib/tau/wearable/theme/brown/images/Scroller/bouncing_top_edge.png__DEL__iEpyiqHYkrP3GQpul3S6C87yG+PyVBWzhrmvxzPf//0= +lib/tau/wearable/theme/brown/images/Scroller/bouncing_top_glow.png__DEL__JmKqm+7nSe0dUkYRduPMoyyfc+Rg9gTVg2J7/pJq5F4= +lib/tau/wearable/theme/brown/images/Selector/b_rotary_selector_edit_mode_ic_add.png__DEL__0uNZLiaoKNgqjrh+2LAZIq6GUFG3lGJHf9PXHbmMeM0= +lib/tau/wearable/theme/brown/images/Selector/b_rotary_selector_edit_mode_ic_delete.png__DEL__vo610xtMs+DvFmvUgCyleSB4MGxZ3pM/RWtx8uxTSg0= +lib/tau/wearable/theme/brown/images/Selector/b_rotary_selector_edit_mode_ic_delete_bg.png__DEL__BKVmFH1pFROxq/f6oH1cJ9PJeaxLGoKNF9DzThgzoCU= +lib/tau/wearable/theme/brown/images/Selector/b_rotary_selector_edit_mode_ic_delete_bg_ef.png__DEL__fvhUuEK9zAM1W8f0Z3gXWnMZO03A8Ae4CxvDcriiJhs= +lib/tau/wearable/theme/brown/images/Slider/b_ic_press_full_circle_bg.png__DEL__V1Ke5HdTcgx64WAnHV4oMLzOMyVITvvc9cTLNAL3jPY= +lib/tau/wearable/theme/brown/images/Slider/b_slider_icon_minus.png__DEL__2QJRn/uDj8ZrhFi6DGeIxKFFVluoZRr7MHAdqKePB74= +lib/tau/wearable/theme/brown/images/Slider/b_slider_icon_plus.png__DEL__4YF4AebfNXlSv1SNQGuuonzGh/f0LUj5e1DZfIWPKZk= +lib/tau/wearable/theme/brown/images/Swipelist/b_logs_icon_actionbar_btn_msg_nor.png__DEL__f6LUaSBsZsugcSX2A3xcaHs+rghK2nFAneqNp7WbOMw= +lib/tau/wearable/theme/brown/images/Swipelist/b_logs_icon_body_btn_call_nor.png__DEL__fl7TG+5Mgf/y6KR8TdSEPcC/teaTrmRLm+lr9TSpOyg= +lib/tau/wearable/theme/brown/images/TextInput/b_list_edit_field_bg.png__DEL__AK4h31Wy4eet4Z4+uT8uMy2Kgcyauiy2tvNj/MjQGKA= +lib/tau/wearable/theme/brown/images/TimePicker/time_picker_bg.png__DEL__pQDmuQlk3a/Nm4n5cfrpBSNmVVZNF0Ap+Mn7WztAzV8= +lib/tau/wearable/theme/brown/images/Title_bar/b_detail_bg_cover.png__DEL__6MewTgCyy7oMiEvq3KkLoNcPwf7TXOAoEtF14XSlYoM= +lib/tau/wearable/theme/brown/tau.css__DEL__TQv9HMruSObr1t6FPaOldowK6px+UQ67FpnnFOc+u3o= +lib/tau/wearable/theme/brown/tau.min.css__DEL__2fQmP+BKfZBT88yUwA6wwyE62PR6Q2F7wWEi+kkzHvM= +lib/tau/wearable/theme/changeable/images/Actionbar/b_more_option.png__DEL__kc1wJhzruwjgLS1vfPkmSQHz67R48K6bh8LpwjDuE+M= +lib/tau/wearable/theme/changeable/images/Actionbar/tw_ic_menu_moreoverflow_holo_dark.png__DEL__qkbuZm3KXZNobT9lxfhl6LdiG8ERnIdYMArm45mDI3g= +lib/tau/wearable/theme/changeable/images/Bottom_button/tw_bottom_btn_bg.png__DEL__DMHAhBUsbboDCd5y8Q83zu3fnJyfF1QmmQzvP7Fjf1U= +lib/tau/wearable/theme/changeable/images/Common/b_more_option_btn_next_page.png__DEL__al6tluFLVe5845VesK7QLEaHmZygqysNWzZOilZUne4= +lib/tau/wearable/theme/changeable/images/Common/b_more_option_btn_previous_page.png__DEL__xArlQu0AmI/ocXV+KTaJc6gg08S6LvGW+qjIpPRXCOg= +lib/tau/wearable/theme/changeable/images/Common/b_rotary_selector_center_pointer.png__DEL__et3cgWnyqfSwP3HokpLXqZug3lwDT7lNt3p0JCrxuBE= +lib/tau/wearable/theme/changeable/images/Common/masking_bg.png__DEL__xg0blL/qm/PlWrv4gh9D5VbFduHT4mEgGpNRugTHpbo= +lib/tau/wearable/theme/changeable/images/Common/tw_no_item_bg.png__DEL__e4K/fWxgP0RPYt1CcPLMU+b0DwOS4fxsN+NcVcOmPKc= +lib/tau/wearable/theme/changeable/images/Controller_icon/Check/ref_btn_check_holo_dark.png__DEL__NWtT1Dj4doBFgGMhL/7RjdD1FG12IcjqwujPGK1PAmY= +lib/tau/wearable/theme/changeable/images/Controller_icon/Check/tw_btn_check_bg_holo_dark.png__DEL__Q0GXfa+7G5Om0sog4aOZZWvd/1ahRg4KyRF6UM2Fnb4= +lib/tau/wearable/theme/changeable/images/Controller_icon/Check/tw_btn_check_holo_dark.png__DEL__0YYITXDSonw21JyxtiJjBf0qFaE0xtQ3RNVNF4qu0rk= +lib/tau/wearable/theme/changeable/images/Controller_icon/On_Off/tw_btn_control_bg_holo_dark.png__DEL__Q0GXfa+7G5Om0sog4aOZZWvd/1ahRg4KyRF6UM2Fnb4= +lib/tau/wearable/theme/changeable/images/Controller_icon/On_Off/tw_btn_control_off_holo_dark.png__DEL__N06Ed3Ihxxo2MldtVQKRq0qYl1eRbuo0zEe2medE1J0= +lib/tau/wearable/theme/changeable/images/Controller_icon/On_Off/tw_btn_control_on_holo_dark.png__DEL__ph0ljcHGNXlJO1dPAiAQgDpRH3+mbMZZ3Ega7lNW/cw= +lib/tau/wearable/theme/changeable/images/Controller_icon/Radio/tw_btn_radio_holo_dark.png__DEL__3i13zlE5UUPepY9sbRmsu3oxYmIBCpM9hwL0YyiVaEI= +lib/tau/wearable/theme/changeable/images/Controller_icon/toggle_changeable.png__DEL__BzK0UNVeSZCohwIBfIkqCru85HE/86q/2MZUEQJWcoU= +lib/tau/wearable/theme/changeable/images/Controller_icon/tw_btn_check_holo_dark.png__DEL__qFC8fOl6Tg7LrLrT62RSQLKSr3GP6O2UwjnG9s7DAck= +lib/tau/wearable/theme/changeable/images/Controller_icon/tw_btn_checkbox_holo_dark.png__DEL__ddihq2E6gV6+g48CtlRBvDFnKx97KWUJxoNr8SQ7sUQ= +lib/tau/wearable/theme/changeable/images/Controller_icon/tw_btn_checkbox_line_holo_dark.png__DEL__elPeRLEot+b5h79G5sudnUNkc6GhAyye82uL2cn9xxc= +lib/tau/wearable/theme/changeable/images/Controller_icon/tw_btn_radio_holo_dark.png__DEL__SYKDGM5OUHfYf65YinTD2vzNNMNUjKEABJaxy/68hv4= +lib/tau/wearable/theme/changeable/images/Controller_icon/tw_btn_radiobox_holo_dark.png__DEL__nwK+t+eTDiAjgXRMhNpbO52ccNtX1T8to7iUEfvtEw0= +lib/tau/wearable/theme/changeable/images/Controller_icon/tw_btn_radiobox_line.png__DEL__JXfJFsytb7Ca9bHn2mZ7J5UVde6gr5WIQWFARUEbSAI= +lib/tau/wearable/theme/changeable/images/Grid/tw_basic_grid_horizontal.png__DEL__Wv4xA+PTooZ5isNMnSm/xQCDNSoMc7W/rUG6m60NEFE= +lib/tau/wearable/theme/changeable/images/Grid/tw_basic_grid_vertical.png__DEL__GE8gsheB4mDn5m7Ef0c0pXefYNoFeybCN6X+IvpJmns= +lib/tau/wearable/theme/changeable/images/Indicator/b_fast_scroll_rollover_bg.png__DEL__BdM1nG2OXAGZ/Jjerne40hr4z+CUVQDaGGDC9NKHbJ4= +lib/tau/wearable/theme/changeable/images/Indicator/b_index_scroll_bg.png__DEL__G1lZylmCqAW+9pxtAqkRcvWmAaqrhmKmFNQ6S+Gfofo= +lib/tau/wearable/theme/changeable/images/Indicator/indicator_horizontal_dot.png__DEL__EmiQ9zOZtFKGu1XIfrFwbGkJApV30yvGAKx9MLuRjdc= +lib/tau/wearable/theme/changeable/images/Indicator/indicator_horizontal_focus_dot.png__DEL__qNnHgB7/8DElpHmMab0YAkGsf15SmvWO52AwSD4brog= +lib/tau/wearable/theme/changeable/images/Indicator/list_scroll_triggle.png__DEL__7GQe3ejeCKtt6U+41MqwJ8q3SaU7cwmfF1sFnFygjCw= +lib/tau/wearable/theme/changeable/images/List/b_body_edge_shadow.png__DEL__xUE9SahpNHNokTSvhfV04BMB0Mdz4wsHy22HYi+93B4= +lib/tau/wearable/theme/changeable/images/List/b_list_focus_bg.png__DEL__50M0LcqxGYrsu0BeMq13DAptfhO6NKmsqRFCnAxFajs= +lib/tau/wearable/theme/changeable/images/List/b_list_focus_bg_108.png__DEL__Ds+e1ERJ7fKikfUF7LTSOFWaGS08JNA/R1xFTPfyGxY= +lib/tau/wearable/theme/changeable/images/List/tw_list_divider.png__DEL__1k/BcizBicjmBjDJEJiy84ZPGZRaIAD1I3IHXAm1igo= +lib/tau/wearable/theme/changeable/images/List/tw_list_sub_line_left.png__DEL__3zbo3CppqPwVpA3f2f9lDsBs06dPHIwlyuqx6pOxOyE= +lib/tau/wearable/theme/changeable/images/List/tw_list_sub_line_right.png__DEL__hc3VpxyhSp3r9EwGpe+A4bbpaOdj9GUyBOPdj80hGTE= +lib/tau/wearable/theme/changeable/images/Popup/b_popup_edge_inner_glow.png__DEL__47LZQG7H45gvdxyu7YNh84m0mqz+lHzw1v3C4y1HKNQ= +lib/tau/wearable/theme/changeable/images/Popup/b_popup_edge_shadow.png__DEL__spXDbatBHTzSFR4N03w93hLrVybuw/XRjZAfneskshI= +lib/tau/wearable/theme/changeable/images/Popup/toast_check.png__DEL__l7tDb2GpWcNj93JU9VgQbbDgqEtDA7bdufKK12uMkPs= +lib/tau/wearable/theme/changeable/images/Popup/tw_ic_popup_left_btn_bg.png__DEL__qoH8usHbcfbB5f0cjjdPuVBueHof14iBqeXKMIQEMRk= +lib/tau/wearable/theme/changeable/images/Popup/tw_ic_popup_right_btn_bg.png__DEL__VFg8qmBikNCdPflDeephVt91R7aPsIIXP9Ra6vjB89w= +lib/tau/wearable/theme/changeable/images/Popup/tw_popup_bg.png__DEL__TphpTsohWyWK9j+2DXpOyvJSydRMq2Av5B0otjA9f9I= +lib/tau/wearable/theme/changeable/images/Popup/tw_popup_bg_cover.png__DEL__vqXi7pK9urSnMFrdVlcc8IFvLcUduAMNIIgbnFvQc4A= +lib/tau/wearable/theme/changeable/images/Popup/tw_toast_popup_bg.png__DEL__HQr3u9+djLy7CqBURR3qMnfvWxQRk+7KsyluA9KadzQ= +lib/tau/wearable/theme/changeable/images/Popup/tw_toast_popup_bg_cover.png__DEL__PTz1Q9tb2jmV/h/NF704nw/+wpelkdig4FdwBoTkXIs= +lib/tau/wearable/theme/changeable/images/Popup/tw_toast_popup_image_bg.png__DEL__3mjrv/wd1wI4zGsbemLKWEA3ARYZyZF/lx8XLE87aSQ= +lib/tau/wearable/theme/changeable/images/Popup/tw_toast_popup_outline.png__DEL__mmYqlg9ixS5F00DUDkRiD8LJARxEPw7FwZpsINr4mwU= +lib/tau/wearable/theme/changeable/images/Processing/processing_bar.png__DEL__h2ItAbWbIgB1+7uU3+RtKHSa7G1cCnNqLu+ENHLGUKQ= +lib/tau/wearable/theme/changeable/images/Processing/processingfull.png__DEL__inphL/shFiOknwr0eLnUc7CGLICqacQ36GidvhaEuJ4= +lib/tau/wearable/theme/changeable/images/Processing/processingsmall.png__DEL__C+hHn6OO+njwAy8GimgFpo+Owigwarpd0+/LdckPdjE= +lib/tau/wearable/theme/changeable/images/Scroller/bouncing_bottom_edge.png__DEL__EwF0JIrEoFCTVRThWK/aNHTfqUzl+oQ5xaoEVeCKakQ= +lib/tau/wearable/theme/changeable/images/Scroller/bouncing_bottom_glow.png__DEL__LBBYbeRMyt+BmcaK6OFcYxXCHatfcBy3WZmMEyeD494= +lib/tau/wearable/theme/changeable/images/Scroller/bouncing_circle_bottom_edge.png__DEL__LrlCUI8SKgQ/Ec+nZhhCBp5rUaVN4zQUfmLeISzlj5Y= +lib/tau/wearable/theme/changeable/images/Scroller/bouncing_circle_bottom_glow.png__DEL__7wNkYofR/ltKHZS5M5XO6KRfaOFY/RLoRk/60QGhqpg= +lib/tau/wearable/theme/changeable/images/Scroller/bouncing_circle_left_edge.png__DEL__qJNgvYmC92FBcdgXqV7u0TCywBwAoRSW9VsYpluHc2M= +lib/tau/wearable/theme/changeable/images/Scroller/bouncing_circle_left_glow.png__DEL__OC1v8JI2m0cBR8OfzterqamyGoAjGLRsxpOkjauhnP4= +lib/tau/wearable/theme/changeable/images/Scroller/bouncing_circle_right_edge.png__DEL__eTu9vGNI+Pn8KBq8Tf4VPNaGJ7srNIKI/Mf/WUAUFos= +lib/tau/wearable/theme/changeable/images/Scroller/bouncing_circle_right_glow.png__DEL__IvH2+9a4Np0dtDtyI/eOLn9bT/MYuzmdNjWTs2GQOTM= +lib/tau/wearable/theme/changeable/images/Scroller/bouncing_circle_top_edge.png__DEL__+s7/n9aNRdSSP5C9yjgGW0DK5+WAZj1GZzaT8FtZRWc= +lib/tau/wearable/theme/changeable/images/Scroller/bouncing_circle_top_glow.png__DEL__GGKaRP+Ko/HHOQHZbsp7JinOllLn/fWiWciLpxCTzVA= +lib/tau/wearable/theme/changeable/images/Scroller/bouncing_left_edge.png__DEL__ezbad2I+Qvqq+HBtFj5fCR73B7u1OpaGp8LbvJiksx8= +lib/tau/wearable/theme/changeable/images/Scroller/bouncing_left_glow.png__DEL__i6payEnlv8vWHucju//UBssYNzmr3SES3nSuWDuJU6c= +lib/tau/wearable/theme/changeable/images/Scroller/bouncing_right_edge.png__DEL__wIF0gTMZq7piBZ9E/+l5UgyW9pSywlBedaETJbK5euE= +lib/tau/wearable/theme/changeable/images/Scroller/bouncing_right_glow.png__DEL__MZ/WXjPf0eoYefs4grYwm+dI4ehPs1D8ElGWv+wLcGk= +lib/tau/wearable/theme/changeable/images/Scroller/bouncing_top_edge.png__DEL__iEpyiqHYkrP3GQpul3S6C87yG+PyVBWzhrmvxzPf//0= +lib/tau/wearable/theme/changeable/images/Scroller/bouncing_top_glow.png__DEL__JmKqm+7nSe0dUkYRduPMoyyfc+Rg9gTVg2J7/pJq5F4= +lib/tau/wearable/theme/changeable/images/Selector/b_rotary_selector_edit_mode_ic_add.png__DEL__0uNZLiaoKNgqjrh+2LAZIq6GUFG3lGJHf9PXHbmMeM0= +lib/tau/wearable/theme/changeable/images/Selector/b_rotary_selector_edit_mode_ic_delete.png__DEL__vo610xtMs+DvFmvUgCyleSB4MGxZ3pM/RWtx8uxTSg0= +lib/tau/wearable/theme/changeable/images/Selector/b_rotary_selector_edit_mode_ic_delete_bg.png__DEL__BKVmFH1pFROxq/f6oH1cJ9PJeaxLGoKNF9DzThgzoCU= +lib/tau/wearable/theme/changeable/images/Selector/b_rotary_selector_edit_mode_ic_delete_bg_ef.png__DEL__fvhUuEK9zAM1W8f0Z3gXWnMZO03A8Ae4CxvDcriiJhs= +lib/tau/wearable/theme/changeable/images/Slider/b_ic_press_full_circle_bg.png__DEL__V1Ke5HdTcgx64WAnHV4oMLzOMyVITvvc9cTLNAL3jPY= +lib/tau/wearable/theme/changeable/images/Slider/b_slider_icon_minus.png__DEL__2QJRn/uDj8ZrhFi6DGeIxKFFVluoZRr7MHAdqKePB74= +lib/tau/wearable/theme/changeable/images/Slider/b_slider_icon_plus.png__DEL__4YF4AebfNXlSv1SNQGuuonzGh/f0LUj5e1DZfIWPKZk= +lib/tau/wearable/theme/changeable/images/Swipelist/b_logs_icon_actionbar_btn_msg_nor.png__DEL__f6LUaSBsZsugcSX2A3xcaHs+rghK2nFAneqNp7WbOMw= +lib/tau/wearable/theme/changeable/images/Swipelist/b_logs_icon_body_btn_call_nor.png__DEL__fl7TG+5Mgf/y6KR8TdSEPcC/teaTrmRLm+lr9TSpOyg= +lib/tau/wearable/theme/changeable/images/TextInput/b_list_edit_field_bg.png__DEL__AK4h31Wy4eet4Z4+uT8uMy2Kgcyauiy2tvNj/MjQGKA= +lib/tau/wearable/theme/changeable/images/TimePicker/time_picker_bg.png__DEL__pQDmuQlk3a/Nm4n5cfrpBSNmVVZNF0Ap+Mn7WztAzV8= +lib/tau/wearable/theme/changeable/images/Title_bar/b_detail_bg_cover.png__DEL__6MewTgCyy7oMiEvq3KkLoNcPwf7TXOAoEtF14XSlYoM= +lib/tau/wearable/theme/changeable/tau.circle.css__DEL__ZrrAANVs6eFlgvPrH3jphJOB28iXV5DYoTvAGUkrSIM= +lib/tau/wearable/theme/changeable/tau.circle.min.css__DEL__FpJOVzu7q+uz3NnFYKiPfwqv2G+aVTIzklNKJX6riCM= +lib/tau/wearable/theme/changeable/tau.circle.min.template__DEL__8UBgJDPoA+ercTDxCbcF1z/d7l27vivl/3DuTozfkKw= +lib/tau/wearable/theme/changeable/tau.circle.template__DEL__QYmeaCeMCYrafGbrGuJAZtNPkT9TuBvLzB/xpouHd7o= +lib/tau/wearable/theme/changeable/tau.css__DEL__Mnd6d8RqbVFwyKwNLKiziXqdDKtay+wmEj79r2jWL+I= +lib/tau/wearable/theme/changeable/tau.min.css__DEL__B9TJ5UITKG2e98hvLldn0ns2ERy/JXEX3lgNB2VpWsM= +lib/tau/wearable/theme/changeable/tau.min.template__DEL__+XRJngMx0882lgPV5wZGiLeMxxg7irSEaXWfsiI5EIk= +lib/tau/wearable/theme/changeable/tau.template__DEL__M9rjopRK33OYpomKSBskxCswnJ6xk2Bwv5X/MLm1INY= +lib/tau/wearable/theme/default/images/Actionbar/b_more_option.png__DEL__kc1wJhzruwjgLS1vfPkmSQHz67R48K6bh8LpwjDuE+M= +lib/tau/wearable/theme/default/images/Actionbar/tw_ic_menu_moreoverflow_holo_dark.png__DEL__qkbuZm3KXZNobT9lxfhl6LdiG8ERnIdYMArm45mDI3g= +lib/tau/wearable/theme/default/images/Bottom_button/tw_bottom_btn_bg.png__DEL__DMHAhBUsbboDCd5y8Q83zu3fnJyfF1QmmQzvP7Fjf1U= +lib/tau/wearable/theme/default/images/Common/b_more_option_btn_next_page.png__DEL__al6tluFLVe5845VesK7QLEaHmZygqysNWzZOilZUne4= +lib/tau/wearable/theme/default/images/Common/b_more_option_btn_previous_page.png__DEL__xArlQu0AmI/ocXV+KTaJc6gg08S6LvGW+qjIpPRXCOg= +lib/tau/wearable/theme/default/images/Common/b_rotary_selector_center_pointer.png__DEL__et3cgWnyqfSwP3HokpLXqZug3lwDT7lNt3p0JCrxuBE= +lib/tau/wearable/theme/default/images/Common/masking_bg.png__DEL__xg0blL/qm/PlWrv4gh9D5VbFduHT4mEgGpNRugTHpbo= +lib/tau/wearable/theme/default/images/Common/tw_no_item_bg.png__DEL__e4K/fWxgP0RPYt1CcPLMU+b0DwOS4fxsN+NcVcOmPKc= +lib/tau/wearable/theme/default/images/Controller_icon/Check/ref_btn_check_holo_dark.png__DEL__NWtT1Dj4doBFgGMhL/7RjdD1FG12IcjqwujPGK1PAmY= +lib/tau/wearable/theme/default/images/Controller_icon/Check/tw_btn_check_bg_holo_dark.png__DEL__Q0GXfa+7G5Om0sog4aOZZWvd/1ahRg4KyRF6UM2Fnb4= +lib/tau/wearable/theme/default/images/Controller_icon/Check/tw_btn_check_holo_dark.png__DEL__0YYITXDSonw21JyxtiJjBf0qFaE0xtQ3RNVNF4qu0rk= +lib/tau/wearable/theme/default/images/Controller_icon/On_Off/tw_btn_control_bg_holo_dark.png__DEL__Q0GXfa+7G5Om0sog4aOZZWvd/1ahRg4KyRF6UM2Fnb4= +lib/tau/wearable/theme/default/images/Controller_icon/On_Off/tw_btn_control_off_holo_dark.png__DEL__N06Ed3Ihxxo2MldtVQKRq0qYl1eRbuo0zEe2medE1J0= +lib/tau/wearable/theme/default/images/Controller_icon/On_Off/tw_btn_control_on_holo_dark.png__DEL__ph0ljcHGNXlJO1dPAiAQgDpRH3+mbMZZ3Ega7lNW/cw= +lib/tau/wearable/theme/default/images/Controller_icon/Radio/tw_btn_radio_holo_dark.png__DEL__3i13zlE5UUPepY9sbRmsu3oxYmIBCpM9hwL0YyiVaEI= +lib/tau/wearable/theme/default/images/Controller_icon/toggle_changeable.png__DEL__BzK0UNVeSZCohwIBfIkqCru85HE/86q/2MZUEQJWcoU= +lib/tau/wearable/theme/default/images/Controller_icon/tw_btn_check_holo_dark.png__DEL__qFC8fOl6Tg7LrLrT62RSQLKSr3GP6O2UwjnG9s7DAck= +lib/tau/wearable/theme/default/images/Controller_icon/tw_btn_checkbox_holo_dark.png__DEL__ddihq2E6gV6+g48CtlRBvDFnKx97KWUJxoNr8SQ7sUQ= +lib/tau/wearable/theme/default/images/Controller_icon/tw_btn_checkbox_line_holo_dark.png__DEL__elPeRLEot+b5h79G5sudnUNkc6GhAyye82uL2cn9xxc= +lib/tau/wearable/theme/default/images/Controller_icon/tw_btn_radio_holo_dark.png__DEL__SYKDGM5OUHfYf65YinTD2vzNNMNUjKEABJaxy/68hv4= +lib/tau/wearable/theme/default/images/Controller_icon/tw_btn_radiobox_holo_dark.png__DEL__nwK+t+eTDiAjgXRMhNpbO52ccNtX1T8to7iUEfvtEw0= +lib/tau/wearable/theme/default/images/Controller_icon/tw_btn_radiobox_line.png__DEL__JXfJFsytb7Ca9bHn2mZ7J5UVde6gr5WIQWFARUEbSAI= +lib/tau/wearable/theme/default/images/Grid/tw_basic_grid_horizontal.png__DEL__Wv4xA+PTooZ5isNMnSm/xQCDNSoMc7W/rUG6m60NEFE= +lib/tau/wearable/theme/default/images/Grid/tw_basic_grid_vertical.png__DEL__GE8gsheB4mDn5m7Ef0c0pXefYNoFeybCN6X+IvpJmns= +lib/tau/wearable/theme/default/images/Indicator/b_fast_scroll_rollover_bg.png__DEL__BdM1nG2OXAGZ/Jjerne40hr4z+CUVQDaGGDC9NKHbJ4= +lib/tau/wearable/theme/default/images/Indicator/b_index_scroll_bg.png__DEL__G1lZylmCqAW+9pxtAqkRcvWmAaqrhmKmFNQ6S+Gfofo= +lib/tau/wearable/theme/default/images/Indicator/indicator_horizontal_dot.png__DEL__EmiQ9zOZtFKGu1XIfrFwbGkJApV30yvGAKx9MLuRjdc= +lib/tau/wearable/theme/default/images/Indicator/indicator_horizontal_focus_dot.png__DEL__qNnHgB7/8DElpHmMab0YAkGsf15SmvWO52AwSD4brog= +lib/tau/wearable/theme/default/images/Indicator/list_scroll_triggle.png__DEL__7GQe3ejeCKtt6U+41MqwJ8q3SaU7cwmfF1sFnFygjCw= +lib/tau/wearable/theme/default/images/List/b_body_edge_shadow.png__DEL__xUE9SahpNHNokTSvhfV04BMB0Mdz4wsHy22HYi+93B4= +lib/tau/wearable/theme/default/images/List/b_list_focus_bg.png__DEL__50M0LcqxGYrsu0BeMq13DAptfhO6NKmsqRFCnAxFajs= +lib/tau/wearable/theme/default/images/List/b_list_focus_bg_108.png__DEL__Ds+e1ERJ7fKikfUF7LTSOFWaGS08JNA/R1xFTPfyGxY= +lib/tau/wearable/theme/default/images/List/tw_list_divider.png__DEL__1k/BcizBicjmBjDJEJiy84ZPGZRaIAD1I3IHXAm1igo= +lib/tau/wearable/theme/default/images/List/tw_list_sub_line_left.png__DEL__3zbo3CppqPwVpA3f2f9lDsBs06dPHIwlyuqx6pOxOyE= +lib/tau/wearable/theme/default/images/List/tw_list_sub_line_right.png__DEL__hc3VpxyhSp3r9EwGpe+A4bbpaOdj9GUyBOPdj80hGTE= +lib/tau/wearable/theme/default/images/Popup/b_popup_edge_inner_glow.png__DEL__47LZQG7H45gvdxyu7YNh84m0mqz+lHzw1v3C4y1HKNQ= +lib/tau/wearable/theme/default/images/Popup/b_popup_edge_shadow.png__DEL__spXDbatBHTzSFR4N03w93hLrVybuw/XRjZAfneskshI= +lib/tau/wearable/theme/default/images/Popup/toast_check.png__DEL__l7tDb2GpWcNj93JU9VgQbbDgqEtDA7bdufKK12uMkPs= +lib/tau/wearable/theme/default/images/Popup/tw_ic_popup_left_btn_bg.png__DEL__qoH8usHbcfbB5f0cjjdPuVBueHof14iBqeXKMIQEMRk= +lib/tau/wearable/theme/default/images/Popup/tw_ic_popup_right_btn_bg.png__DEL__VFg8qmBikNCdPflDeephVt91R7aPsIIXP9Ra6vjB89w= +lib/tau/wearable/theme/default/images/Popup/tw_popup_bg.png__DEL__TphpTsohWyWK9j+2DXpOyvJSydRMq2Av5B0otjA9f9I= +lib/tau/wearable/theme/default/images/Popup/tw_popup_bg_cover.png__DEL__vqXi7pK9urSnMFrdVlcc8IFvLcUduAMNIIgbnFvQc4A= +lib/tau/wearable/theme/default/images/Popup/tw_toast_popup_bg.png__DEL__HQr3u9+djLy7CqBURR3qMnfvWxQRk+7KsyluA9KadzQ= +lib/tau/wearable/theme/default/images/Popup/tw_toast_popup_bg_cover.png__DEL__PTz1Q9tb2jmV/h/NF704nw/+wpelkdig4FdwBoTkXIs= +lib/tau/wearable/theme/default/images/Popup/tw_toast_popup_image_bg.png__DEL__3mjrv/wd1wI4zGsbemLKWEA3ARYZyZF/lx8XLE87aSQ= +lib/tau/wearable/theme/default/images/Popup/tw_toast_popup_outline.png__DEL__mmYqlg9ixS5F00DUDkRiD8LJARxEPw7FwZpsINr4mwU= +lib/tau/wearable/theme/default/images/Processing/processing_bar.png__DEL__h2ItAbWbIgB1+7uU3+RtKHSa7G1cCnNqLu+ENHLGUKQ= +lib/tau/wearable/theme/default/images/Processing/processingfull.png__DEL__inphL/shFiOknwr0eLnUc7CGLICqacQ36GidvhaEuJ4= +lib/tau/wearable/theme/default/images/Processing/processingsmall.png__DEL__C+hHn6OO+njwAy8GimgFpo+Owigwarpd0+/LdckPdjE= +lib/tau/wearable/theme/default/images/Scroller/bouncing_bottom_edge.png__DEL__EwF0JIrEoFCTVRThWK/aNHTfqUzl+oQ5xaoEVeCKakQ= +lib/tau/wearable/theme/default/images/Scroller/bouncing_bottom_glow.png__DEL__LBBYbeRMyt+BmcaK6OFcYxXCHatfcBy3WZmMEyeD494= +lib/tau/wearable/theme/default/images/Scroller/bouncing_circle_bottom_edge.png__DEL__LrlCUI8SKgQ/Ec+nZhhCBp5rUaVN4zQUfmLeISzlj5Y= +lib/tau/wearable/theme/default/images/Scroller/bouncing_circle_bottom_glow.png__DEL__7wNkYofR/ltKHZS5M5XO6KRfaOFY/RLoRk/60QGhqpg= +lib/tau/wearable/theme/default/images/Scroller/bouncing_circle_left_edge.png__DEL__qJNgvYmC92FBcdgXqV7u0TCywBwAoRSW9VsYpluHc2M= +lib/tau/wearable/theme/default/images/Scroller/bouncing_circle_left_glow.png__DEL__OC1v8JI2m0cBR8OfzterqamyGoAjGLRsxpOkjauhnP4= +lib/tau/wearable/theme/default/images/Scroller/bouncing_circle_right_edge.png__DEL__eTu9vGNI+Pn8KBq8Tf4VPNaGJ7srNIKI/Mf/WUAUFos= +lib/tau/wearable/theme/default/images/Scroller/bouncing_circle_right_glow.png__DEL__IvH2+9a4Np0dtDtyI/eOLn9bT/MYuzmdNjWTs2GQOTM= +lib/tau/wearable/theme/default/images/Scroller/bouncing_circle_top_edge.png__DEL__+s7/n9aNRdSSP5C9yjgGW0DK5+WAZj1GZzaT8FtZRWc= +lib/tau/wearable/theme/default/images/Scroller/bouncing_circle_top_glow.png__DEL__GGKaRP+Ko/HHOQHZbsp7JinOllLn/fWiWciLpxCTzVA= +lib/tau/wearable/theme/default/images/Scroller/bouncing_left_edge.png__DEL__ezbad2I+Qvqq+HBtFj5fCR73B7u1OpaGp8LbvJiksx8= +lib/tau/wearable/theme/default/images/Scroller/bouncing_left_glow.png__DEL__i6payEnlv8vWHucju//UBssYNzmr3SES3nSuWDuJU6c= +lib/tau/wearable/theme/default/images/Scroller/bouncing_right_edge.png__DEL__wIF0gTMZq7piBZ9E/+l5UgyW9pSywlBedaETJbK5euE= +lib/tau/wearable/theme/default/images/Scroller/bouncing_right_glow.png__DEL__MZ/WXjPf0eoYefs4grYwm+dI4ehPs1D8ElGWv+wLcGk= +lib/tau/wearable/theme/default/images/Scroller/bouncing_top_edge.png__DEL__iEpyiqHYkrP3GQpul3S6C87yG+PyVBWzhrmvxzPf//0= +lib/tau/wearable/theme/default/images/Scroller/bouncing_top_glow.png__DEL__JmKqm+7nSe0dUkYRduPMoyyfc+Rg9gTVg2J7/pJq5F4= +lib/tau/wearable/theme/default/images/Selector/b_rotary_selector_edit_mode_ic_add.png__DEL__0uNZLiaoKNgqjrh+2LAZIq6GUFG3lGJHf9PXHbmMeM0= +lib/tau/wearable/theme/default/images/Selector/b_rotary_selector_edit_mode_ic_delete.png__DEL__vo610xtMs+DvFmvUgCyleSB4MGxZ3pM/RWtx8uxTSg0= +lib/tau/wearable/theme/default/images/Selector/b_rotary_selector_edit_mode_ic_delete_bg.png__DEL__BKVmFH1pFROxq/f6oH1cJ9PJeaxLGoKNF9DzThgzoCU= +lib/tau/wearable/theme/default/images/Selector/b_rotary_selector_edit_mode_ic_delete_bg_ef.png__DEL__fvhUuEK9zAM1W8f0Z3gXWnMZO03A8Ae4CxvDcriiJhs= +lib/tau/wearable/theme/default/images/Slider/b_ic_press_full_circle_bg.png__DEL__V1Ke5HdTcgx64WAnHV4oMLzOMyVITvvc9cTLNAL3jPY= +lib/tau/wearable/theme/default/images/Slider/b_slider_icon_minus.png__DEL__2QJRn/uDj8ZrhFi6DGeIxKFFVluoZRr7MHAdqKePB74= +lib/tau/wearable/theme/default/images/Slider/b_slider_icon_plus.png__DEL__4YF4AebfNXlSv1SNQGuuonzGh/f0LUj5e1DZfIWPKZk= +lib/tau/wearable/theme/default/images/Swipelist/b_logs_icon_actionbar_btn_msg_nor.png__DEL__f6LUaSBsZsugcSX2A3xcaHs+rghK2nFAneqNp7WbOMw= +lib/tau/wearable/theme/default/images/Swipelist/b_logs_icon_body_btn_call_nor.png__DEL__fl7TG+5Mgf/y6KR8TdSEPcC/teaTrmRLm+lr9TSpOyg= +lib/tau/wearable/theme/default/images/TextInput/b_list_edit_field_bg.png__DEL__AK4h31Wy4eet4Z4+uT8uMy2Kgcyauiy2tvNj/MjQGKA= +lib/tau/wearable/theme/default/images/TimePicker/time_picker_bg.png__DEL__pQDmuQlk3a/Nm4n5cfrpBSNmVVZNF0Ap+Mn7WztAzV8= +lib/tau/wearable/theme/default/images/Title_bar/b_detail_bg_cover.png__DEL__6MewTgCyy7oMiEvq3KkLoNcPwf7TXOAoEtF14XSlYoM= +lib/tau/wearable/theme/default/tau.circle.css__DEL__ZrrAANVs6eFlgvPrH3jphJOB28iXV5DYoTvAGUkrSIM= +lib/tau/wearable/theme/default/tau.circle.min.css__DEL__FpJOVzu7q+uz3NnFYKiPfwqv2G+aVTIzklNKJX6riCM= +lib/tau/wearable/theme/default/tau.circle.min.template__DEL__8UBgJDPoA+ercTDxCbcF1z/d7l27vivl/3DuTozfkKw= +lib/tau/wearable/theme/default/tau.circle.template__DEL__QYmeaCeMCYrafGbrGuJAZtNPkT9TuBvLzB/xpouHd7o= +lib/tau/wearable/theme/default/tau.css__DEL__Mnd6d8RqbVFwyKwNLKiziXqdDKtay+wmEj79r2jWL+I= +lib/tau/wearable/theme/default/tau.min.css__DEL__B9TJ5UITKG2e98hvLldn0ns2ERy/JXEX3lgNB2VpWsM= +lib/tau/wearable/theme/default/tau.min.template__DEL__+XRJngMx0882lgPV5wZGiLeMxxg7irSEaXWfsiI5EIk= +lib/tau/wearable/theme/default/tau.template__DEL__M9rjopRK33OYpomKSBskxCswnJ6xk2Bwv5X/MLm1INY= +author-signature.xml__DEL__A2GEI3miRFryCPtE70bXgh9OraRoP1D/BJ3nTSE7LqQ= diff --git a/.sign/author-signature.xml b/.sign/author-signature.xml new file mode 100644 index 0000000..31f8b34 --- /dev/null +++ b/.sign/author-signature.xml @@ -0,0 +1,1419 @@ + + + + + + +GxzJ0cd5vZqiKf7ncDFwwfV6IMiUf/4dVt2aKMI0ZR0= + + + +zZAIh+P4GlT4d5svP2PxBGKqK1UNw13Oma2g1+aPQfg= + + + +vPjxjOppORd6hn9Bw4sh06gqtDoJzoFbV/8e9FyIdvk= + + + +zhJg3MyxJjPq3+GmqMVGlGPGyiv0vf09mhSP/tTjNGQ= + + + +Sqm38++Ho3FHKe6iDgIJiIvxquD2zmSHhSaVXubSd7Q= + + + +0+TbSqg2TfiaBBu+OpgUziLxj8xqrvGgo5rR4vZRB9U= + + + ++i5daekAbnIThj3rFOl0UxpMhcCvNBzIUCR5tkh0Flg= + + + +R4988xI3tkQMAcLGLOROY5Wp8YTDrI0X90uMvrydtQw= + + + +0ukTrmj+rXT674x9SPLCs6ezcKZxSRyjZoVvWGQpi9U= + + + +yKlkTLe4BKz6si0fZBr4fD7FoL4zInZkUu183ZDOcdo= + + + +yJz2NgNxUqtqEZfUVD+KGHVE2iL5tccsaEap7PbHqio= + + + +kc1wJhzruwjgLS1vfPkmSQHz67R48K6bh8LpwjDuE+M= + + + +qkbuZm3KXZNobT9lxfhl6LdiG8ERnIdYMArm45mDI3g= + + + +DMHAhBUsbboDCd5y8Q83zu3fnJyfF1QmmQzvP7Fjf1U= + + + +al6tluFLVe5845VesK7QLEaHmZygqysNWzZOilZUne4= + + + +xArlQu0AmI/ocXV+KTaJc6gg08S6LvGW+qjIpPRXCOg= + + + +et3cgWnyqfSwP3HokpLXqZug3lwDT7lNt3p0JCrxuBE= + + + +xg0blL/qm/PlWrv4gh9D5VbFduHT4mEgGpNRugTHpbo= + + + +e4K/fWxgP0RPYt1CcPLMU+b0DwOS4fxsN+NcVcOmPKc= + + + +NWtT1Dj4doBFgGMhL/7RjdD1FG12IcjqwujPGK1PAmY= + + + +Q0GXfa+7G5Om0sog4aOZZWvd/1ahRg4KyRF6UM2Fnb4= + + + +0YYITXDSonw21JyxtiJjBf0qFaE0xtQ3RNVNF4qu0rk= + + + +Q0GXfa+7G5Om0sog4aOZZWvd/1ahRg4KyRF6UM2Fnb4= + + + +N06Ed3Ihxxo2MldtVQKRq0qYl1eRbuo0zEe2medE1J0= + + + +ph0ljcHGNXlJO1dPAiAQgDpRH3+mbMZZ3Ega7lNW/cw= + + + +3i13zlE5UUPepY9sbRmsu3oxYmIBCpM9hwL0YyiVaEI= + + + +BzK0UNVeSZCohwIBfIkqCru85HE/86q/2MZUEQJWcoU= + + + +qFC8fOl6Tg7LrLrT62RSQLKSr3GP6O2UwjnG9s7DAck= + + + +ddihq2E6gV6+g48CtlRBvDFnKx97KWUJxoNr8SQ7sUQ= + + + +elPeRLEot+b5h79G5sudnUNkc6GhAyye82uL2cn9xxc= + + + +SYKDGM5OUHfYf65YinTD2vzNNMNUjKEABJaxy/68hv4= + + + +nwK+t+eTDiAjgXRMhNpbO52ccNtX1T8to7iUEfvtEw0= + + + +JXfJFsytb7Ca9bHn2mZ7J5UVde6gr5WIQWFARUEbSAI= + + + +Wv4xA+PTooZ5isNMnSm/xQCDNSoMc7W/rUG6m60NEFE= + + + +GE8gsheB4mDn5m7Ef0c0pXefYNoFeybCN6X+IvpJmns= + + + +BdM1nG2OXAGZ/Jjerne40hr4z+CUVQDaGGDC9NKHbJ4= + + + +G1lZylmCqAW+9pxtAqkRcvWmAaqrhmKmFNQ6S+Gfofo= + + + +EmiQ9zOZtFKGu1XIfrFwbGkJApV30yvGAKx9MLuRjdc= + + + +qNnHgB7/8DElpHmMab0YAkGsf15SmvWO52AwSD4brog= + + + +7GQe3ejeCKtt6U+41MqwJ8q3SaU7cwmfF1sFnFygjCw= + + + +xUE9SahpNHNokTSvhfV04BMB0Mdz4wsHy22HYi+93B4= + + + +50M0LcqxGYrsu0BeMq13DAptfhO6NKmsqRFCnAxFajs= + + + +Ds+e1ERJ7fKikfUF7LTSOFWaGS08JNA/R1xFTPfyGxY= + + + +1k/BcizBicjmBjDJEJiy84ZPGZRaIAD1I3IHXAm1igo= + + + +3zbo3CppqPwVpA3f2f9lDsBs06dPHIwlyuqx6pOxOyE= + + + +hc3VpxyhSp3r9EwGpe+A4bbpaOdj9GUyBOPdj80hGTE= + + + +47LZQG7H45gvdxyu7YNh84m0mqz+lHzw1v3C4y1HKNQ= + + + +spXDbatBHTzSFR4N03w93hLrVybuw/XRjZAfneskshI= + + + +l7tDb2GpWcNj93JU9VgQbbDgqEtDA7bdufKK12uMkPs= + + + +qoH8usHbcfbB5f0cjjdPuVBueHof14iBqeXKMIQEMRk= + + + +VFg8qmBikNCdPflDeephVt91R7aPsIIXP9Ra6vjB89w= + + + +TphpTsohWyWK9j+2DXpOyvJSydRMq2Av5B0otjA9f9I= + + + +vqXi7pK9urSnMFrdVlcc8IFvLcUduAMNIIgbnFvQc4A= + + + +HQr3u9+djLy7CqBURR3qMnfvWxQRk+7KsyluA9KadzQ= + + + +PTz1Q9tb2jmV/h/NF704nw/+wpelkdig4FdwBoTkXIs= + + + +3mjrv/wd1wI4zGsbemLKWEA3ARYZyZF/lx8XLE87aSQ= + + + +mmYqlg9ixS5F00DUDkRiD8LJARxEPw7FwZpsINr4mwU= + + + +h2ItAbWbIgB1+7uU3+RtKHSa7G1cCnNqLu+ENHLGUKQ= + + + +inphL/shFiOknwr0eLnUc7CGLICqacQ36GidvhaEuJ4= + + + +C+hHn6OO+njwAy8GimgFpo+Owigwarpd0+/LdckPdjE= + + + +EwF0JIrEoFCTVRThWK/aNHTfqUzl+oQ5xaoEVeCKakQ= + + + +LBBYbeRMyt+BmcaK6OFcYxXCHatfcBy3WZmMEyeD494= + + + +LrlCUI8SKgQ/Ec+nZhhCBp5rUaVN4zQUfmLeISzlj5Y= + + + +7wNkYofR/ltKHZS5M5XO6KRfaOFY/RLoRk/60QGhqpg= + + + +qJNgvYmC92FBcdgXqV7u0TCywBwAoRSW9VsYpluHc2M= + + + +OC1v8JI2m0cBR8OfzterqamyGoAjGLRsxpOkjauhnP4= + + + +eTu9vGNI+Pn8KBq8Tf4VPNaGJ7srNIKI/Mf/WUAUFos= + + + +IvH2+9a4Np0dtDtyI/eOLn9bT/MYuzmdNjWTs2GQOTM= + + + ++s7/n9aNRdSSP5C9yjgGW0DK5+WAZj1GZzaT8FtZRWc= + + + +GGKaRP+Ko/HHOQHZbsp7JinOllLn/fWiWciLpxCTzVA= + + + +ezbad2I+Qvqq+HBtFj5fCR73B7u1OpaGp8LbvJiksx8= + + + +i6payEnlv8vWHucju//UBssYNzmr3SES3nSuWDuJU6c= + + + +wIF0gTMZq7piBZ9E/+l5UgyW9pSywlBedaETJbK5euE= + + + +MZ/WXjPf0eoYefs4grYwm+dI4ehPs1D8ElGWv+wLcGk= + + + +iEpyiqHYkrP3GQpul3S6C87yG+PyVBWzhrmvxzPf//0= + + + +JmKqm+7nSe0dUkYRduPMoyyfc+Rg9gTVg2J7/pJq5F4= + + + +0uNZLiaoKNgqjrh+2LAZIq6GUFG3lGJHf9PXHbmMeM0= + + + +vo610xtMs+DvFmvUgCyleSB4MGxZ3pM/RWtx8uxTSg0= + + + +BKVmFH1pFROxq/f6oH1cJ9PJeaxLGoKNF9DzThgzoCU= + + + +fvhUuEK9zAM1W8f0Z3gXWnMZO03A8Ae4CxvDcriiJhs= + + + +V1Ke5HdTcgx64WAnHV4oMLzOMyVITvvc9cTLNAL3jPY= + + + +2QJRn/uDj8ZrhFi6DGeIxKFFVluoZRr7MHAdqKePB74= + + + +4YF4AebfNXlSv1SNQGuuonzGh/f0LUj5e1DZfIWPKZk= + + + +f6LUaSBsZsugcSX2A3xcaHs+rghK2nFAneqNp7WbOMw= + + + +fl7TG+5Mgf/y6KR8TdSEPcC/teaTrmRLm+lr9TSpOyg= + + + +AK4h31Wy4eet4Z4+uT8uMy2Kgcyauiy2tvNj/MjQGKA= + + + +pQDmuQlk3a/Nm4n5cfrpBSNmVVZNF0Ap+Mn7WztAzV8= + + + +6MewTgCyy7oMiEvq3KkLoNcPwf7TXOAoEtF14XSlYoM= + + + +Mnd6d8RqbVFwyKwNLKiziXqdDKtay+wmEj79r2jWL+I= + + + +B9TJ5UITKG2e98hvLldn0ns2ERy/JXEX3lgNB2VpWsM= + + + +kc1wJhzruwjgLS1vfPkmSQHz67R48K6bh8LpwjDuE+M= + + + +qkbuZm3KXZNobT9lxfhl6LdiG8ERnIdYMArm45mDI3g= + + + +DMHAhBUsbboDCd5y8Q83zu3fnJyfF1QmmQzvP7Fjf1U= + + + +al6tluFLVe5845VesK7QLEaHmZygqysNWzZOilZUne4= + + + +xArlQu0AmI/ocXV+KTaJc6gg08S6LvGW+qjIpPRXCOg= + + + +et3cgWnyqfSwP3HokpLXqZug3lwDT7lNt3p0JCrxuBE= + + + +xg0blL/qm/PlWrv4gh9D5VbFduHT4mEgGpNRugTHpbo= + + + +e4K/fWxgP0RPYt1CcPLMU+b0DwOS4fxsN+NcVcOmPKc= + + + +NWtT1Dj4doBFgGMhL/7RjdD1FG12IcjqwujPGK1PAmY= + + + +Q0GXfa+7G5Om0sog4aOZZWvd/1ahRg4KyRF6UM2Fnb4= + + + +0YYITXDSonw21JyxtiJjBf0qFaE0xtQ3RNVNF4qu0rk= + + + +Q0GXfa+7G5Om0sog4aOZZWvd/1ahRg4KyRF6UM2Fnb4= + + + +N06Ed3Ihxxo2MldtVQKRq0qYl1eRbuo0zEe2medE1J0= + + + +ph0ljcHGNXlJO1dPAiAQgDpRH3+mbMZZ3Ega7lNW/cw= + + + +3i13zlE5UUPepY9sbRmsu3oxYmIBCpM9hwL0YyiVaEI= + + + +BzK0UNVeSZCohwIBfIkqCru85HE/86q/2MZUEQJWcoU= + + + +qFC8fOl6Tg7LrLrT62RSQLKSr3GP6O2UwjnG9s7DAck= + + + +ddihq2E6gV6+g48CtlRBvDFnKx97KWUJxoNr8SQ7sUQ= + + + +elPeRLEot+b5h79G5sudnUNkc6GhAyye82uL2cn9xxc= + + + +SYKDGM5OUHfYf65YinTD2vzNNMNUjKEABJaxy/68hv4= + + + +nwK+t+eTDiAjgXRMhNpbO52ccNtX1T8to7iUEfvtEw0= + + + +JXfJFsytb7Ca9bHn2mZ7J5UVde6gr5WIQWFARUEbSAI= + + + +Wv4xA+PTooZ5isNMnSm/xQCDNSoMc7W/rUG6m60NEFE= + + + +GE8gsheB4mDn5m7Ef0c0pXefYNoFeybCN6X+IvpJmns= + + + +BdM1nG2OXAGZ/Jjerne40hr4z+CUVQDaGGDC9NKHbJ4= + + + +G1lZylmCqAW+9pxtAqkRcvWmAaqrhmKmFNQ6S+Gfofo= + + + +EmiQ9zOZtFKGu1XIfrFwbGkJApV30yvGAKx9MLuRjdc= + + + +qNnHgB7/8DElpHmMab0YAkGsf15SmvWO52AwSD4brog= + + + +7GQe3ejeCKtt6U+41MqwJ8q3SaU7cwmfF1sFnFygjCw= + + + +xUE9SahpNHNokTSvhfV04BMB0Mdz4wsHy22HYi+93B4= + + + +50M0LcqxGYrsu0BeMq13DAptfhO6NKmsqRFCnAxFajs= + + + +Ds+e1ERJ7fKikfUF7LTSOFWaGS08JNA/R1xFTPfyGxY= + + + +1k/BcizBicjmBjDJEJiy84ZPGZRaIAD1I3IHXAm1igo= + + + +3zbo3CppqPwVpA3f2f9lDsBs06dPHIwlyuqx6pOxOyE= + + + +hc3VpxyhSp3r9EwGpe+A4bbpaOdj9GUyBOPdj80hGTE= + + + +47LZQG7H45gvdxyu7YNh84m0mqz+lHzw1v3C4y1HKNQ= + + + +spXDbatBHTzSFR4N03w93hLrVybuw/XRjZAfneskshI= + + + +l7tDb2GpWcNj93JU9VgQbbDgqEtDA7bdufKK12uMkPs= + + + +qoH8usHbcfbB5f0cjjdPuVBueHof14iBqeXKMIQEMRk= + + + +VFg8qmBikNCdPflDeephVt91R7aPsIIXP9Ra6vjB89w= + + + +TphpTsohWyWK9j+2DXpOyvJSydRMq2Av5B0otjA9f9I= + + + +vqXi7pK9urSnMFrdVlcc8IFvLcUduAMNIIgbnFvQc4A= + + + +HQr3u9+djLy7CqBURR3qMnfvWxQRk+7KsyluA9KadzQ= + + + +PTz1Q9tb2jmV/h/NF704nw/+wpelkdig4FdwBoTkXIs= + + + +3mjrv/wd1wI4zGsbemLKWEA3ARYZyZF/lx8XLE87aSQ= + + + +mmYqlg9ixS5F00DUDkRiD8LJARxEPw7FwZpsINr4mwU= + + + +h2ItAbWbIgB1+7uU3+RtKHSa7G1cCnNqLu+ENHLGUKQ= + + + +inphL/shFiOknwr0eLnUc7CGLICqacQ36GidvhaEuJ4= + + + +C+hHn6OO+njwAy8GimgFpo+Owigwarpd0+/LdckPdjE= + + + +EwF0JIrEoFCTVRThWK/aNHTfqUzl+oQ5xaoEVeCKakQ= + + + +LBBYbeRMyt+BmcaK6OFcYxXCHatfcBy3WZmMEyeD494= + + + +LrlCUI8SKgQ/Ec+nZhhCBp5rUaVN4zQUfmLeISzlj5Y= + + + +7wNkYofR/ltKHZS5M5XO6KRfaOFY/RLoRk/60QGhqpg= + + + +qJNgvYmC92FBcdgXqV7u0TCywBwAoRSW9VsYpluHc2M= + + + +OC1v8JI2m0cBR8OfzterqamyGoAjGLRsxpOkjauhnP4= + + + +eTu9vGNI+Pn8KBq8Tf4VPNaGJ7srNIKI/Mf/WUAUFos= + + + +IvH2+9a4Np0dtDtyI/eOLn9bT/MYuzmdNjWTs2GQOTM= + + + ++s7/n9aNRdSSP5C9yjgGW0DK5+WAZj1GZzaT8FtZRWc= + + + +GGKaRP+Ko/HHOQHZbsp7JinOllLn/fWiWciLpxCTzVA= + + + +ezbad2I+Qvqq+HBtFj5fCR73B7u1OpaGp8LbvJiksx8= + + + +i6payEnlv8vWHucju//UBssYNzmr3SES3nSuWDuJU6c= + + + +wIF0gTMZq7piBZ9E/+l5UgyW9pSywlBedaETJbK5euE= + + + +MZ/WXjPf0eoYefs4grYwm+dI4ehPs1D8ElGWv+wLcGk= + + + +iEpyiqHYkrP3GQpul3S6C87yG+PyVBWzhrmvxzPf//0= + + + +JmKqm+7nSe0dUkYRduPMoyyfc+Rg9gTVg2J7/pJq5F4= + + + +0uNZLiaoKNgqjrh+2LAZIq6GUFG3lGJHf9PXHbmMeM0= + + + +vo610xtMs+DvFmvUgCyleSB4MGxZ3pM/RWtx8uxTSg0= + + + +BKVmFH1pFROxq/f6oH1cJ9PJeaxLGoKNF9DzThgzoCU= + + + +fvhUuEK9zAM1W8f0Z3gXWnMZO03A8Ae4CxvDcriiJhs= + + + +V1Ke5HdTcgx64WAnHV4oMLzOMyVITvvc9cTLNAL3jPY= + + + +2QJRn/uDj8ZrhFi6DGeIxKFFVluoZRr7MHAdqKePB74= + + + +4YF4AebfNXlSv1SNQGuuonzGh/f0LUj5e1DZfIWPKZk= + + + +f6LUaSBsZsugcSX2A3xcaHs+rghK2nFAneqNp7WbOMw= + + + +fl7TG+5Mgf/y6KR8TdSEPcC/teaTrmRLm+lr9TSpOyg= + + + +AK4h31Wy4eet4Z4+uT8uMy2Kgcyauiy2tvNj/MjQGKA= + + + +pQDmuQlk3a/Nm4n5cfrpBSNmVVZNF0Ap+Mn7WztAzV8= + + + +6MewTgCyy7oMiEvq3KkLoNcPwf7TXOAoEtF14XSlYoM= + + + +TQv9HMruSObr1t6FPaOldowK6px+UQ67FpnnFOc+u3o= + + + +2fQmP+BKfZBT88yUwA6wwyE62PR6Q2F7wWEi+kkzHvM= + + + +kc1wJhzruwjgLS1vfPkmSQHz67R48K6bh8LpwjDuE+M= + + + +qkbuZm3KXZNobT9lxfhl6LdiG8ERnIdYMArm45mDI3g= + + + +DMHAhBUsbboDCd5y8Q83zu3fnJyfF1QmmQzvP7Fjf1U= + + + +al6tluFLVe5845VesK7QLEaHmZygqysNWzZOilZUne4= + + + +xArlQu0AmI/ocXV+KTaJc6gg08S6LvGW+qjIpPRXCOg= + + + +et3cgWnyqfSwP3HokpLXqZug3lwDT7lNt3p0JCrxuBE= + + + +xg0blL/qm/PlWrv4gh9D5VbFduHT4mEgGpNRugTHpbo= + + + +e4K/fWxgP0RPYt1CcPLMU+b0DwOS4fxsN+NcVcOmPKc= + + + +NWtT1Dj4doBFgGMhL/7RjdD1FG12IcjqwujPGK1PAmY= + + + +Q0GXfa+7G5Om0sog4aOZZWvd/1ahRg4KyRF6UM2Fnb4= + + + +0YYITXDSonw21JyxtiJjBf0qFaE0xtQ3RNVNF4qu0rk= + + + +Q0GXfa+7G5Om0sog4aOZZWvd/1ahRg4KyRF6UM2Fnb4= + + + +N06Ed3Ihxxo2MldtVQKRq0qYl1eRbuo0zEe2medE1J0= + + + +ph0ljcHGNXlJO1dPAiAQgDpRH3+mbMZZ3Ega7lNW/cw= + + + +3i13zlE5UUPepY9sbRmsu3oxYmIBCpM9hwL0YyiVaEI= + + + +BzK0UNVeSZCohwIBfIkqCru85HE/86q/2MZUEQJWcoU= + + + +qFC8fOl6Tg7LrLrT62RSQLKSr3GP6O2UwjnG9s7DAck= + + + +ddihq2E6gV6+g48CtlRBvDFnKx97KWUJxoNr8SQ7sUQ= + + + +elPeRLEot+b5h79G5sudnUNkc6GhAyye82uL2cn9xxc= + + + +SYKDGM5OUHfYf65YinTD2vzNNMNUjKEABJaxy/68hv4= + + + +nwK+t+eTDiAjgXRMhNpbO52ccNtX1T8to7iUEfvtEw0= + + + +JXfJFsytb7Ca9bHn2mZ7J5UVde6gr5WIQWFARUEbSAI= + + + +Wv4xA+PTooZ5isNMnSm/xQCDNSoMc7W/rUG6m60NEFE= + + + +GE8gsheB4mDn5m7Ef0c0pXefYNoFeybCN6X+IvpJmns= + + + +BdM1nG2OXAGZ/Jjerne40hr4z+CUVQDaGGDC9NKHbJ4= + + + +G1lZylmCqAW+9pxtAqkRcvWmAaqrhmKmFNQ6S+Gfofo= + + + +EmiQ9zOZtFKGu1XIfrFwbGkJApV30yvGAKx9MLuRjdc= + + + +qNnHgB7/8DElpHmMab0YAkGsf15SmvWO52AwSD4brog= + + + +7GQe3ejeCKtt6U+41MqwJ8q3SaU7cwmfF1sFnFygjCw= + + + +xUE9SahpNHNokTSvhfV04BMB0Mdz4wsHy22HYi+93B4= + + + +50M0LcqxGYrsu0BeMq13DAptfhO6NKmsqRFCnAxFajs= + + + +Ds+e1ERJ7fKikfUF7LTSOFWaGS08JNA/R1xFTPfyGxY= + + + +1k/BcizBicjmBjDJEJiy84ZPGZRaIAD1I3IHXAm1igo= + + + +3zbo3CppqPwVpA3f2f9lDsBs06dPHIwlyuqx6pOxOyE= + + + +hc3VpxyhSp3r9EwGpe+A4bbpaOdj9GUyBOPdj80hGTE= + + + +47LZQG7H45gvdxyu7YNh84m0mqz+lHzw1v3C4y1HKNQ= + + + +spXDbatBHTzSFR4N03w93hLrVybuw/XRjZAfneskshI= + + + +l7tDb2GpWcNj93JU9VgQbbDgqEtDA7bdufKK12uMkPs= + + + +qoH8usHbcfbB5f0cjjdPuVBueHof14iBqeXKMIQEMRk= + + + +VFg8qmBikNCdPflDeephVt91R7aPsIIXP9Ra6vjB89w= + + + +TphpTsohWyWK9j+2DXpOyvJSydRMq2Av5B0otjA9f9I= + + + +vqXi7pK9urSnMFrdVlcc8IFvLcUduAMNIIgbnFvQc4A= + + + +HQr3u9+djLy7CqBURR3qMnfvWxQRk+7KsyluA9KadzQ= + + + +PTz1Q9tb2jmV/h/NF704nw/+wpelkdig4FdwBoTkXIs= + + + +3mjrv/wd1wI4zGsbemLKWEA3ARYZyZF/lx8XLE87aSQ= + + + +mmYqlg9ixS5F00DUDkRiD8LJARxEPw7FwZpsINr4mwU= + + + +h2ItAbWbIgB1+7uU3+RtKHSa7G1cCnNqLu+ENHLGUKQ= + + + +inphL/shFiOknwr0eLnUc7CGLICqacQ36GidvhaEuJ4= + + + +C+hHn6OO+njwAy8GimgFpo+Owigwarpd0+/LdckPdjE= + + + +EwF0JIrEoFCTVRThWK/aNHTfqUzl+oQ5xaoEVeCKakQ= + + + +LBBYbeRMyt+BmcaK6OFcYxXCHatfcBy3WZmMEyeD494= + + + +LrlCUI8SKgQ/Ec+nZhhCBp5rUaVN4zQUfmLeISzlj5Y= + + + +7wNkYofR/ltKHZS5M5XO6KRfaOFY/RLoRk/60QGhqpg= + + + +qJNgvYmC92FBcdgXqV7u0TCywBwAoRSW9VsYpluHc2M= + + + +OC1v8JI2m0cBR8OfzterqamyGoAjGLRsxpOkjauhnP4= + + + +eTu9vGNI+Pn8KBq8Tf4VPNaGJ7srNIKI/Mf/WUAUFos= + + + +IvH2+9a4Np0dtDtyI/eOLn9bT/MYuzmdNjWTs2GQOTM= + + + ++s7/n9aNRdSSP5C9yjgGW0DK5+WAZj1GZzaT8FtZRWc= + + + +GGKaRP+Ko/HHOQHZbsp7JinOllLn/fWiWciLpxCTzVA= + + + +ezbad2I+Qvqq+HBtFj5fCR73B7u1OpaGp8LbvJiksx8= + + + +i6payEnlv8vWHucju//UBssYNzmr3SES3nSuWDuJU6c= + + + +wIF0gTMZq7piBZ9E/+l5UgyW9pSywlBedaETJbK5euE= + + + +MZ/WXjPf0eoYefs4grYwm+dI4ehPs1D8ElGWv+wLcGk= + + + +iEpyiqHYkrP3GQpul3S6C87yG+PyVBWzhrmvxzPf//0= + + + +JmKqm+7nSe0dUkYRduPMoyyfc+Rg9gTVg2J7/pJq5F4= + + + +0uNZLiaoKNgqjrh+2LAZIq6GUFG3lGJHf9PXHbmMeM0= + + + +vo610xtMs+DvFmvUgCyleSB4MGxZ3pM/RWtx8uxTSg0= + + + +BKVmFH1pFROxq/f6oH1cJ9PJeaxLGoKNF9DzThgzoCU= + + + +fvhUuEK9zAM1W8f0Z3gXWnMZO03A8Ae4CxvDcriiJhs= + + + +V1Ke5HdTcgx64WAnHV4oMLzOMyVITvvc9cTLNAL3jPY= + + + +2QJRn/uDj8ZrhFi6DGeIxKFFVluoZRr7MHAdqKePB74= + + + +4YF4AebfNXlSv1SNQGuuonzGh/f0LUj5e1DZfIWPKZk= + + + +f6LUaSBsZsugcSX2A3xcaHs+rghK2nFAneqNp7WbOMw= + + + +fl7TG+5Mgf/y6KR8TdSEPcC/teaTrmRLm+lr9TSpOyg= + + + +AK4h31Wy4eet4Z4+uT8uMy2Kgcyauiy2tvNj/MjQGKA= + + + +pQDmuQlk3a/Nm4n5cfrpBSNmVVZNF0Ap+Mn7WztAzV8= + + + +6MewTgCyy7oMiEvq3KkLoNcPwf7TXOAoEtF14XSlYoM= + + + +ZrrAANVs6eFlgvPrH3jphJOB28iXV5DYoTvAGUkrSIM= + + + +FpJOVzu7q+uz3NnFYKiPfwqv2G+aVTIzklNKJX6riCM= + + + +8UBgJDPoA+ercTDxCbcF1z/d7l27vivl/3DuTozfkKw= + + + +QYmeaCeMCYrafGbrGuJAZtNPkT9TuBvLzB/xpouHd7o= + + + +Mnd6d8RqbVFwyKwNLKiziXqdDKtay+wmEj79r2jWL+I= + + + +B9TJ5UITKG2e98hvLldn0ns2ERy/JXEX3lgNB2VpWsM= + + + ++XRJngMx0882lgPV5wZGiLeMxxg7irSEaXWfsiI5EIk= + + + +M9rjopRK33OYpomKSBskxCswnJ6xk2Bwv5X/MLm1INY= + + + +kc1wJhzruwjgLS1vfPkmSQHz67R48K6bh8LpwjDuE+M= + + + +qkbuZm3KXZNobT9lxfhl6LdiG8ERnIdYMArm45mDI3g= + + + +DMHAhBUsbboDCd5y8Q83zu3fnJyfF1QmmQzvP7Fjf1U= + + + +al6tluFLVe5845VesK7QLEaHmZygqysNWzZOilZUne4= + + + +xArlQu0AmI/ocXV+KTaJc6gg08S6LvGW+qjIpPRXCOg= + + + +et3cgWnyqfSwP3HokpLXqZug3lwDT7lNt3p0JCrxuBE= + + + +xg0blL/qm/PlWrv4gh9D5VbFduHT4mEgGpNRugTHpbo= + + + +e4K/fWxgP0RPYt1CcPLMU+b0DwOS4fxsN+NcVcOmPKc= + + + +NWtT1Dj4doBFgGMhL/7RjdD1FG12IcjqwujPGK1PAmY= + + + +Q0GXfa+7G5Om0sog4aOZZWvd/1ahRg4KyRF6UM2Fnb4= + + + +0YYITXDSonw21JyxtiJjBf0qFaE0xtQ3RNVNF4qu0rk= + + + +Q0GXfa+7G5Om0sog4aOZZWvd/1ahRg4KyRF6UM2Fnb4= + + + +N06Ed3Ihxxo2MldtVQKRq0qYl1eRbuo0zEe2medE1J0= + + + +ph0ljcHGNXlJO1dPAiAQgDpRH3+mbMZZ3Ega7lNW/cw= + + + +3i13zlE5UUPepY9sbRmsu3oxYmIBCpM9hwL0YyiVaEI= + + + +BzK0UNVeSZCohwIBfIkqCru85HE/86q/2MZUEQJWcoU= + + + +qFC8fOl6Tg7LrLrT62RSQLKSr3GP6O2UwjnG9s7DAck= + + + +ddihq2E6gV6+g48CtlRBvDFnKx97KWUJxoNr8SQ7sUQ= + + + +elPeRLEot+b5h79G5sudnUNkc6GhAyye82uL2cn9xxc= + + + +SYKDGM5OUHfYf65YinTD2vzNNMNUjKEABJaxy/68hv4= + + + +nwK+t+eTDiAjgXRMhNpbO52ccNtX1T8to7iUEfvtEw0= + + + +JXfJFsytb7Ca9bHn2mZ7J5UVde6gr5WIQWFARUEbSAI= + + + +Wv4xA+PTooZ5isNMnSm/xQCDNSoMc7W/rUG6m60NEFE= + + + +GE8gsheB4mDn5m7Ef0c0pXefYNoFeybCN6X+IvpJmns= + + + +BdM1nG2OXAGZ/Jjerne40hr4z+CUVQDaGGDC9NKHbJ4= + + + +G1lZylmCqAW+9pxtAqkRcvWmAaqrhmKmFNQ6S+Gfofo= + + + +EmiQ9zOZtFKGu1XIfrFwbGkJApV30yvGAKx9MLuRjdc= + + + +qNnHgB7/8DElpHmMab0YAkGsf15SmvWO52AwSD4brog= + + + +7GQe3ejeCKtt6U+41MqwJ8q3SaU7cwmfF1sFnFygjCw= + + + +xUE9SahpNHNokTSvhfV04BMB0Mdz4wsHy22HYi+93B4= + + + +50M0LcqxGYrsu0BeMq13DAptfhO6NKmsqRFCnAxFajs= + + + +Ds+e1ERJ7fKikfUF7LTSOFWaGS08JNA/R1xFTPfyGxY= + + + +1k/BcizBicjmBjDJEJiy84ZPGZRaIAD1I3IHXAm1igo= + + + +3zbo3CppqPwVpA3f2f9lDsBs06dPHIwlyuqx6pOxOyE= + + + +hc3VpxyhSp3r9EwGpe+A4bbpaOdj9GUyBOPdj80hGTE= + + + +47LZQG7H45gvdxyu7YNh84m0mqz+lHzw1v3C4y1HKNQ= + + + +spXDbatBHTzSFR4N03w93hLrVybuw/XRjZAfneskshI= + + + +l7tDb2GpWcNj93JU9VgQbbDgqEtDA7bdufKK12uMkPs= + + + +qoH8usHbcfbB5f0cjjdPuVBueHof14iBqeXKMIQEMRk= + + + +VFg8qmBikNCdPflDeephVt91R7aPsIIXP9Ra6vjB89w= + + + +TphpTsohWyWK9j+2DXpOyvJSydRMq2Av5B0otjA9f9I= + + + +vqXi7pK9urSnMFrdVlcc8IFvLcUduAMNIIgbnFvQc4A= + + + +HQr3u9+djLy7CqBURR3qMnfvWxQRk+7KsyluA9KadzQ= + + + +PTz1Q9tb2jmV/h/NF704nw/+wpelkdig4FdwBoTkXIs= + + + +3mjrv/wd1wI4zGsbemLKWEA3ARYZyZF/lx8XLE87aSQ= + + + +mmYqlg9ixS5F00DUDkRiD8LJARxEPw7FwZpsINr4mwU= + + + +h2ItAbWbIgB1+7uU3+RtKHSa7G1cCnNqLu+ENHLGUKQ= + + + +inphL/shFiOknwr0eLnUc7CGLICqacQ36GidvhaEuJ4= + + + +C+hHn6OO+njwAy8GimgFpo+Owigwarpd0+/LdckPdjE= + + + +EwF0JIrEoFCTVRThWK/aNHTfqUzl+oQ5xaoEVeCKakQ= + + + +LBBYbeRMyt+BmcaK6OFcYxXCHatfcBy3WZmMEyeD494= + + + +LrlCUI8SKgQ/Ec+nZhhCBp5rUaVN4zQUfmLeISzlj5Y= + + + +7wNkYofR/ltKHZS5M5XO6KRfaOFY/RLoRk/60QGhqpg= + + + +qJNgvYmC92FBcdgXqV7u0TCywBwAoRSW9VsYpluHc2M= + + + +OC1v8JI2m0cBR8OfzterqamyGoAjGLRsxpOkjauhnP4= + + + +eTu9vGNI+Pn8KBq8Tf4VPNaGJ7srNIKI/Mf/WUAUFos= + + + +IvH2+9a4Np0dtDtyI/eOLn9bT/MYuzmdNjWTs2GQOTM= + + + ++s7/n9aNRdSSP5C9yjgGW0DK5+WAZj1GZzaT8FtZRWc= + + + +GGKaRP+Ko/HHOQHZbsp7JinOllLn/fWiWciLpxCTzVA= + + + +ezbad2I+Qvqq+HBtFj5fCR73B7u1OpaGp8LbvJiksx8= + + + +i6payEnlv8vWHucju//UBssYNzmr3SES3nSuWDuJU6c= + + + +wIF0gTMZq7piBZ9E/+l5UgyW9pSywlBedaETJbK5euE= + + + +MZ/WXjPf0eoYefs4grYwm+dI4ehPs1D8ElGWv+wLcGk= + + + +iEpyiqHYkrP3GQpul3S6C87yG+PyVBWzhrmvxzPf//0= + + + +JmKqm+7nSe0dUkYRduPMoyyfc+Rg9gTVg2J7/pJq5F4= + + + +0uNZLiaoKNgqjrh+2LAZIq6GUFG3lGJHf9PXHbmMeM0= + + + +vo610xtMs+DvFmvUgCyleSB4MGxZ3pM/RWtx8uxTSg0= + + + +BKVmFH1pFROxq/f6oH1cJ9PJeaxLGoKNF9DzThgzoCU= + + + +fvhUuEK9zAM1W8f0Z3gXWnMZO03A8Ae4CxvDcriiJhs= + + + +V1Ke5HdTcgx64WAnHV4oMLzOMyVITvvc9cTLNAL3jPY= + + + +2QJRn/uDj8ZrhFi6DGeIxKFFVluoZRr7MHAdqKePB74= + + + +4YF4AebfNXlSv1SNQGuuonzGh/f0LUj5e1DZfIWPKZk= + + + +f6LUaSBsZsugcSX2A3xcaHs+rghK2nFAneqNp7WbOMw= + + + +fl7TG+5Mgf/y6KR8TdSEPcC/teaTrmRLm+lr9TSpOyg= + + + +AK4h31Wy4eet4Z4+uT8uMy2Kgcyauiy2tvNj/MjQGKA= + + + +pQDmuQlk3a/Nm4n5cfrpBSNmVVZNF0Ap+Mn7WztAzV8= + + + +6MewTgCyy7oMiEvq3KkLoNcPwf7TXOAoEtF14XSlYoM= + + + +ZrrAANVs6eFlgvPrH3jphJOB28iXV5DYoTvAGUkrSIM= + + + +FpJOVzu7q+uz3NnFYKiPfwqv2G+aVTIzklNKJX6riCM= + + + +8UBgJDPoA+ercTDxCbcF1z/d7l27vivl/3DuTozfkKw= + + + +QYmeaCeMCYrafGbrGuJAZtNPkT9TuBvLzB/xpouHd7o= + + + +Mnd6d8RqbVFwyKwNLKiziXqdDKtay+wmEj79r2jWL+I= + + + +B9TJ5UITKG2e98hvLldn0ns2ERy/JXEX3lgNB2VpWsM= + + + ++XRJngMx0882lgPV5wZGiLeMxxg7irSEaXWfsiI5EIk= + + + +M9rjopRK33OYpomKSBskxCswnJ6xk2Bwv5X/MLm1INY= + + + + + + +lpo8tUDs054eLlBQXiDPVDVKfw30ZZdtkRs1jd7H5K8= + + + +ThKvSyV6YjP9FqfFs09t48REUT/unmL/MAQYf66k2V7lnjf9C0SGHVXHSa6536w3lnthX7e8BgMZ +Hd+exuAMCAUxFUlyPN+hnmOqgp9H0g7T/NoTH3rb22ImZw3fTnAXv7ipAV8ekPjjc8qbWJnsNWoZ +6TPtszDC2bSKZjgZdfoEOGb7BKNPjTHdbDbmPHQBja6pHD+gg0xZLGX8JQRmMuKzDMq7JBKCJ+uw +Z7tP+7UBzCeox9xwVGBGv4eqQH/WlqzM1b1ZYCg2O7cax/fFX38LQRmCmrSj0L9rftKqjxNQ6d/X +KgRB8/i/e72xZbvxauWSNHrdZOPACjPy3KsjmQ== + + + + +MIIDpTCCAo2gAwIBAgIBATANBgkqhkiG9w0BAQsFADCBsDELMAkGA1UEBhMCS1IxFDASBgNVBAgM +C1NvdXRoIEtvcmVhMQ4wDAYDVQQHDAVTdXdvbjEmMCQGA1UECgwdU2Ftc3VuZyBFbGVjdHJvbmlj +cyBDby4sIEx0ZC4xDzANBgNVBAsMBk1vYmlsZTEgMB4GA1UEAwwXU2Ftc3VuZyBBdXRob3IgQ0Eg +Q2xhc3MxIDAeBgkqhkiG9w0BCQEWEXRpemVuQHNhbXN1bmcuY29tMB4XDTE5MDQwNjIyNTMzOFoX +DTIwMDQwNTIyNTMzOFowXzELMAkGA1UEBhMCREUxCzAJBgNVBAgTAkJZMRIwEAYDVQQHEwlOdXJl +bWJlcmcxCTAHBgNVBAoTADEJMAcGA1UECxMAMRkwFwYDVQQDExBQYXRyaWNrIE1vZXNzbGVyMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoHQV1Tn4L25z0XlVD9QAOXjSDbiibPW2qAg/ +RiWAeSbrCcCADQt+ZUrUF5I+CQn3jwfmRUq1lPYQ/ovirsICtNQ/uAeUWLQjLh7IbXIUhr8ftM7g +BF/h6bCf4+dsRF2CGF7RG4BMEEztndM6LuQwU2XYXzLeXCxSNq7K2twZd32stokgTpy6NoO3L7YK +vhYjxzcebIJpJjo4mxib6SD3ms5Lg/nC8PKtg/WH+AhK9Ig9zXCiKGqhRtXsWvPTrFlGsWmg2k5h +lBgD3OBfbK3pmBnWC8g5SyRuerl/XLYQ4P6LDTJSoXRTgTTPkdbKdMUgb1lGHOZGqgx3oelXZsJt +9QIDAQABoxowGDAJBgNVHRMEAjAAMAsGA1UdDwQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEAWuH8 +kUT+DaRzBMyc75TFw3nxUD9rn0rv++p8JJ3K6WIscz6y1KcimwFPAdlvduQ9CsSpqPHPtCOWEkNL +2Vh8fc/+9mcPNfiXe0dLx7WpJoXpbXbcATq1PzAmoXQdFMCIn40Rs9mpdRZorAXeUU0vysBB2ihn +bRS498wqtgzyBrhXqZW74GDe4D/J3W41S0vp9cH5oOijN/HyMg67X8K7Ldvf9JgHQQBTa0Awidn3 +Vk5vnDvyHD1KgerX27DId4l2t6mfxFmKslAjuKjzm8DnmkeYvr5PeMFS8vdqbtLjmgHNcOcpflnc +8U4+vliiRW1IUmEFFk3r93I0m8cQ4gD3fg== + + +MIIDmzCCAoOgAwIBAgICYygwDQYJKoZIhvcNAQELBQAwXjEaMBgGA1UECgwRVGl6ZW4gQXNzb2Np +YXRpb24xGjAYBgNVBAsMEVRpemVuIEFzc29jaWF0aW9uMSQwIgYDVQQDDBtUaXplbiBEZXZlbG9w +ZXJzIFJvb3QgQ2xhc3MwHhcNMTMxMjMwMTUwNTU4WhcNMjgxMjI2MTUwNTU4WjCBsDELMAkGA1UE +BhMCS1IxFDASBgNVBAgMC1NvdXRoIEtvcmVhMQ4wDAYDVQQHDAVTdXdvbjEmMCQGA1UECgwdU2Ft +c3VuZyBFbGVjdHJvbmljcyBDby4sIEx0ZC4xDzANBgNVBAsMBk1vYmlsZTEgMB4GA1UEAwwXU2Ft +c3VuZyBBdXRob3IgQ0EgQ2xhc3MxIDAeBgkqhkiG9w0BCQEWEXRpemVuQHNhbXN1bmcuY29tMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs+tRBgnKJs8j7BFg8/UftqbqTCYBV3Jrg1vK +YvEuoTfntYz2uT2SO67raiCsZBAYvJnP54ExkdV++UgB7BDGniWz7bA1pYKak5kNK5jtLQt2DmZX +3qgaLjMyoAz+293CxrBQO4h8NaTQGsO/WLpeQq2Y1ZEnHsq+EUn90H6Vm0HNW+KUayGPYdey+QSW +iiv+L++TSuHrw0b16GYn83emiTnKTCmwpSOZ712Gy9kccl46/K4C8skEDVZjTk9s7r/MN9ZNZsqR +brT/3AYcrF4ao8ipwlHK91WJBXXaiQICvp/dNfCSDWpTWy7z4XmgP16pSLnfgZlwEwWfiaavHRNM +mwIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB/ZlNMTzlIHqC3mFSq +ptuQDZG96XnYqiWsbYkqGgNhcq6c/B3TQsg7Z8cxXY/eqJQDN5gbrIpiUugMRdSOpAEcxF3lwd5k +oOzVLn+3I7x1k6Q4pZdi1fJx+1XjCtrQgPqtvwM77urNqIA1MSG6HUPxYAKkRKjWPsg346E8S/c1 +Hq4UVBYEFcDC467uvWtYjxjEVQTmNUaUcQLU9P6VEL4QW+t7V54IN6IJDr9HoOGSgApxIBDDU46b +MUwl+yK0GPvhrviwfVPkfmys1hn5N+gWectQVpBB1gbfy2KlLCCvW/Kl1VmtYz1kWwTyG8bwcjE0 +GLkwKNN5lPod+FmMhuW9 + + + + + \ No newline at end of file diff --git a/.sign/signature1.xml b/.sign/signature1.xml new file mode 100644 index 0000000..b5968fd --- /dev/null +++ b/.sign/signature1.xml @@ -0,0 +1,1428 @@ + + + + + + +A2GEI3miRFryCPtE70bXgh9OraRoP1D/BJ3nTSE7LqQ= + + + +GxzJ0cd5vZqiKf7ncDFwwfV6IMiUf/4dVt2aKMI0ZR0= + + + +zZAIh+P4GlT4d5svP2PxBGKqK1UNw13Oma2g1+aPQfg= + + + +vPjxjOppORd6hn9Bw4sh06gqtDoJzoFbV/8e9FyIdvk= + + + +zhJg3MyxJjPq3+GmqMVGlGPGyiv0vf09mhSP/tTjNGQ= + + + +Sqm38++Ho3FHKe6iDgIJiIvxquD2zmSHhSaVXubSd7Q= + + + +0+TbSqg2TfiaBBu+OpgUziLxj8xqrvGgo5rR4vZRB9U= + + + ++i5daekAbnIThj3rFOl0UxpMhcCvNBzIUCR5tkh0Flg= + + + +R4988xI3tkQMAcLGLOROY5Wp8YTDrI0X90uMvrydtQw= + + + +0ukTrmj+rXT674x9SPLCs6ezcKZxSRyjZoVvWGQpi9U= + + + +yKlkTLe4BKz6si0fZBr4fD7FoL4zInZkUu183ZDOcdo= + + + +yJz2NgNxUqtqEZfUVD+KGHVE2iL5tccsaEap7PbHqio= + + + +kc1wJhzruwjgLS1vfPkmSQHz67R48K6bh8LpwjDuE+M= + + + +qkbuZm3KXZNobT9lxfhl6LdiG8ERnIdYMArm45mDI3g= + + + +DMHAhBUsbboDCd5y8Q83zu3fnJyfF1QmmQzvP7Fjf1U= + + + +al6tluFLVe5845VesK7QLEaHmZygqysNWzZOilZUne4= + + + +xArlQu0AmI/ocXV+KTaJc6gg08S6LvGW+qjIpPRXCOg= + + + +et3cgWnyqfSwP3HokpLXqZug3lwDT7lNt3p0JCrxuBE= + + + +xg0blL/qm/PlWrv4gh9D5VbFduHT4mEgGpNRugTHpbo= + + + +e4K/fWxgP0RPYt1CcPLMU+b0DwOS4fxsN+NcVcOmPKc= + + + +NWtT1Dj4doBFgGMhL/7RjdD1FG12IcjqwujPGK1PAmY= + + + +Q0GXfa+7G5Om0sog4aOZZWvd/1ahRg4KyRF6UM2Fnb4= + + + +0YYITXDSonw21JyxtiJjBf0qFaE0xtQ3RNVNF4qu0rk= + + + +Q0GXfa+7G5Om0sog4aOZZWvd/1ahRg4KyRF6UM2Fnb4= + + + +N06Ed3Ihxxo2MldtVQKRq0qYl1eRbuo0zEe2medE1J0= + + + +ph0ljcHGNXlJO1dPAiAQgDpRH3+mbMZZ3Ega7lNW/cw= + + + +3i13zlE5UUPepY9sbRmsu3oxYmIBCpM9hwL0YyiVaEI= + + + +BzK0UNVeSZCohwIBfIkqCru85HE/86q/2MZUEQJWcoU= + + + +qFC8fOl6Tg7LrLrT62RSQLKSr3GP6O2UwjnG9s7DAck= + + + +ddihq2E6gV6+g48CtlRBvDFnKx97KWUJxoNr8SQ7sUQ= + + + +elPeRLEot+b5h79G5sudnUNkc6GhAyye82uL2cn9xxc= + + + +SYKDGM5OUHfYf65YinTD2vzNNMNUjKEABJaxy/68hv4= + + + +nwK+t+eTDiAjgXRMhNpbO52ccNtX1T8to7iUEfvtEw0= + + + +JXfJFsytb7Ca9bHn2mZ7J5UVde6gr5WIQWFARUEbSAI= + + + +Wv4xA+PTooZ5isNMnSm/xQCDNSoMc7W/rUG6m60NEFE= + + + +GE8gsheB4mDn5m7Ef0c0pXefYNoFeybCN6X+IvpJmns= + + + +BdM1nG2OXAGZ/Jjerne40hr4z+CUVQDaGGDC9NKHbJ4= + + + +G1lZylmCqAW+9pxtAqkRcvWmAaqrhmKmFNQ6S+Gfofo= + + + +EmiQ9zOZtFKGu1XIfrFwbGkJApV30yvGAKx9MLuRjdc= + + + +qNnHgB7/8DElpHmMab0YAkGsf15SmvWO52AwSD4brog= + + + +7GQe3ejeCKtt6U+41MqwJ8q3SaU7cwmfF1sFnFygjCw= + + + +xUE9SahpNHNokTSvhfV04BMB0Mdz4wsHy22HYi+93B4= + + + +50M0LcqxGYrsu0BeMq13DAptfhO6NKmsqRFCnAxFajs= + + + +Ds+e1ERJ7fKikfUF7LTSOFWaGS08JNA/R1xFTPfyGxY= + + + +1k/BcizBicjmBjDJEJiy84ZPGZRaIAD1I3IHXAm1igo= + + + +3zbo3CppqPwVpA3f2f9lDsBs06dPHIwlyuqx6pOxOyE= + + + +hc3VpxyhSp3r9EwGpe+A4bbpaOdj9GUyBOPdj80hGTE= + + + +47LZQG7H45gvdxyu7YNh84m0mqz+lHzw1v3C4y1HKNQ= + + + +spXDbatBHTzSFR4N03w93hLrVybuw/XRjZAfneskshI= + + + +l7tDb2GpWcNj93JU9VgQbbDgqEtDA7bdufKK12uMkPs= + + + +qoH8usHbcfbB5f0cjjdPuVBueHof14iBqeXKMIQEMRk= + + + +VFg8qmBikNCdPflDeephVt91R7aPsIIXP9Ra6vjB89w= + + + +TphpTsohWyWK9j+2DXpOyvJSydRMq2Av5B0otjA9f9I= + + + +vqXi7pK9urSnMFrdVlcc8IFvLcUduAMNIIgbnFvQc4A= + + + +HQr3u9+djLy7CqBURR3qMnfvWxQRk+7KsyluA9KadzQ= + + + +PTz1Q9tb2jmV/h/NF704nw/+wpelkdig4FdwBoTkXIs= + + + +3mjrv/wd1wI4zGsbemLKWEA3ARYZyZF/lx8XLE87aSQ= + + + +mmYqlg9ixS5F00DUDkRiD8LJARxEPw7FwZpsINr4mwU= + + + +h2ItAbWbIgB1+7uU3+RtKHSa7G1cCnNqLu+ENHLGUKQ= + + + +inphL/shFiOknwr0eLnUc7CGLICqacQ36GidvhaEuJ4= + + + +C+hHn6OO+njwAy8GimgFpo+Owigwarpd0+/LdckPdjE= + + + +EwF0JIrEoFCTVRThWK/aNHTfqUzl+oQ5xaoEVeCKakQ= + + + +LBBYbeRMyt+BmcaK6OFcYxXCHatfcBy3WZmMEyeD494= + + + +LrlCUI8SKgQ/Ec+nZhhCBp5rUaVN4zQUfmLeISzlj5Y= + + + +7wNkYofR/ltKHZS5M5XO6KRfaOFY/RLoRk/60QGhqpg= + + + +qJNgvYmC92FBcdgXqV7u0TCywBwAoRSW9VsYpluHc2M= + + + +OC1v8JI2m0cBR8OfzterqamyGoAjGLRsxpOkjauhnP4= + + + +eTu9vGNI+Pn8KBq8Tf4VPNaGJ7srNIKI/Mf/WUAUFos= + + + +IvH2+9a4Np0dtDtyI/eOLn9bT/MYuzmdNjWTs2GQOTM= + + + ++s7/n9aNRdSSP5C9yjgGW0DK5+WAZj1GZzaT8FtZRWc= + + + +GGKaRP+Ko/HHOQHZbsp7JinOllLn/fWiWciLpxCTzVA= + + + +ezbad2I+Qvqq+HBtFj5fCR73B7u1OpaGp8LbvJiksx8= + + + +i6payEnlv8vWHucju//UBssYNzmr3SES3nSuWDuJU6c= + + + +wIF0gTMZq7piBZ9E/+l5UgyW9pSywlBedaETJbK5euE= + + + +MZ/WXjPf0eoYefs4grYwm+dI4ehPs1D8ElGWv+wLcGk= + + + +iEpyiqHYkrP3GQpul3S6C87yG+PyVBWzhrmvxzPf//0= + + + +JmKqm+7nSe0dUkYRduPMoyyfc+Rg9gTVg2J7/pJq5F4= + + + +0uNZLiaoKNgqjrh+2LAZIq6GUFG3lGJHf9PXHbmMeM0= + + + +vo610xtMs+DvFmvUgCyleSB4MGxZ3pM/RWtx8uxTSg0= + + + +BKVmFH1pFROxq/f6oH1cJ9PJeaxLGoKNF9DzThgzoCU= + + + +fvhUuEK9zAM1W8f0Z3gXWnMZO03A8Ae4CxvDcriiJhs= + + + +V1Ke5HdTcgx64WAnHV4oMLzOMyVITvvc9cTLNAL3jPY= + + + +2QJRn/uDj8ZrhFi6DGeIxKFFVluoZRr7MHAdqKePB74= + + + +4YF4AebfNXlSv1SNQGuuonzGh/f0LUj5e1DZfIWPKZk= + + + +f6LUaSBsZsugcSX2A3xcaHs+rghK2nFAneqNp7WbOMw= + + + +fl7TG+5Mgf/y6KR8TdSEPcC/teaTrmRLm+lr9TSpOyg= + + + +AK4h31Wy4eet4Z4+uT8uMy2Kgcyauiy2tvNj/MjQGKA= + + + +pQDmuQlk3a/Nm4n5cfrpBSNmVVZNF0Ap+Mn7WztAzV8= + + + +6MewTgCyy7oMiEvq3KkLoNcPwf7TXOAoEtF14XSlYoM= + + + +Mnd6d8RqbVFwyKwNLKiziXqdDKtay+wmEj79r2jWL+I= + + + +B9TJ5UITKG2e98hvLldn0ns2ERy/JXEX3lgNB2VpWsM= + + + +kc1wJhzruwjgLS1vfPkmSQHz67R48K6bh8LpwjDuE+M= + + + +qkbuZm3KXZNobT9lxfhl6LdiG8ERnIdYMArm45mDI3g= + + + +DMHAhBUsbboDCd5y8Q83zu3fnJyfF1QmmQzvP7Fjf1U= + + + +al6tluFLVe5845VesK7QLEaHmZygqysNWzZOilZUne4= + + + +xArlQu0AmI/ocXV+KTaJc6gg08S6LvGW+qjIpPRXCOg= + + + +et3cgWnyqfSwP3HokpLXqZug3lwDT7lNt3p0JCrxuBE= + + + +xg0blL/qm/PlWrv4gh9D5VbFduHT4mEgGpNRugTHpbo= + + + +e4K/fWxgP0RPYt1CcPLMU+b0DwOS4fxsN+NcVcOmPKc= + + + +NWtT1Dj4doBFgGMhL/7RjdD1FG12IcjqwujPGK1PAmY= + + + +Q0GXfa+7G5Om0sog4aOZZWvd/1ahRg4KyRF6UM2Fnb4= + + + +0YYITXDSonw21JyxtiJjBf0qFaE0xtQ3RNVNF4qu0rk= + + + +Q0GXfa+7G5Om0sog4aOZZWvd/1ahRg4KyRF6UM2Fnb4= + + + +N06Ed3Ihxxo2MldtVQKRq0qYl1eRbuo0zEe2medE1J0= + + + +ph0ljcHGNXlJO1dPAiAQgDpRH3+mbMZZ3Ega7lNW/cw= + + + +3i13zlE5UUPepY9sbRmsu3oxYmIBCpM9hwL0YyiVaEI= + + + +BzK0UNVeSZCohwIBfIkqCru85HE/86q/2MZUEQJWcoU= + + + +qFC8fOl6Tg7LrLrT62RSQLKSr3GP6O2UwjnG9s7DAck= + + + +ddihq2E6gV6+g48CtlRBvDFnKx97KWUJxoNr8SQ7sUQ= + + + +elPeRLEot+b5h79G5sudnUNkc6GhAyye82uL2cn9xxc= + + + +SYKDGM5OUHfYf65YinTD2vzNNMNUjKEABJaxy/68hv4= + + + +nwK+t+eTDiAjgXRMhNpbO52ccNtX1T8to7iUEfvtEw0= + + + +JXfJFsytb7Ca9bHn2mZ7J5UVde6gr5WIQWFARUEbSAI= + + + +Wv4xA+PTooZ5isNMnSm/xQCDNSoMc7W/rUG6m60NEFE= + + + +GE8gsheB4mDn5m7Ef0c0pXefYNoFeybCN6X+IvpJmns= + + + +BdM1nG2OXAGZ/Jjerne40hr4z+CUVQDaGGDC9NKHbJ4= + + + +G1lZylmCqAW+9pxtAqkRcvWmAaqrhmKmFNQ6S+Gfofo= + + + +EmiQ9zOZtFKGu1XIfrFwbGkJApV30yvGAKx9MLuRjdc= + + + +qNnHgB7/8DElpHmMab0YAkGsf15SmvWO52AwSD4brog= + + + +7GQe3ejeCKtt6U+41MqwJ8q3SaU7cwmfF1sFnFygjCw= + + + +xUE9SahpNHNokTSvhfV04BMB0Mdz4wsHy22HYi+93B4= + + + +50M0LcqxGYrsu0BeMq13DAptfhO6NKmsqRFCnAxFajs= + + + +Ds+e1ERJ7fKikfUF7LTSOFWaGS08JNA/R1xFTPfyGxY= + + + +1k/BcizBicjmBjDJEJiy84ZPGZRaIAD1I3IHXAm1igo= + + + +3zbo3CppqPwVpA3f2f9lDsBs06dPHIwlyuqx6pOxOyE= + + + +hc3VpxyhSp3r9EwGpe+A4bbpaOdj9GUyBOPdj80hGTE= + + + +47LZQG7H45gvdxyu7YNh84m0mqz+lHzw1v3C4y1HKNQ= + + + +spXDbatBHTzSFR4N03w93hLrVybuw/XRjZAfneskshI= + + + +l7tDb2GpWcNj93JU9VgQbbDgqEtDA7bdufKK12uMkPs= + + + +qoH8usHbcfbB5f0cjjdPuVBueHof14iBqeXKMIQEMRk= + + + +VFg8qmBikNCdPflDeephVt91R7aPsIIXP9Ra6vjB89w= + + + +TphpTsohWyWK9j+2DXpOyvJSydRMq2Av5B0otjA9f9I= + + + +vqXi7pK9urSnMFrdVlcc8IFvLcUduAMNIIgbnFvQc4A= + + + +HQr3u9+djLy7CqBURR3qMnfvWxQRk+7KsyluA9KadzQ= + + + +PTz1Q9tb2jmV/h/NF704nw/+wpelkdig4FdwBoTkXIs= + + + +3mjrv/wd1wI4zGsbemLKWEA3ARYZyZF/lx8XLE87aSQ= + + + +mmYqlg9ixS5F00DUDkRiD8LJARxEPw7FwZpsINr4mwU= + + + +h2ItAbWbIgB1+7uU3+RtKHSa7G1cCnNqLu+ENHLGUKQ= + + + +inphL/shFiOknwr0eLnUc7CGLICqacQ36GidvhaEuJ4= + + + +C+hHn6OO+njwAy8GimgFpo+Owigwarpd0+/LdckPdjE= + + + +EwF0JIrEoFCTVRThWK/aNHTfqUzl+oQ5xaoEVeCKakQ= + + + +LBBYbeRMyt+BmcaK6OFcYxXCHatfcBy3WZmMEyeD494= + + + +LrlCUI8SKgQ/Ec+nZhhCBp5rUaVN4zQUfmLeISzlj5Y= + + + +7wNkYofR/ltKHZS5M5XO6KRfaOFY/RLoRk/60QGhqpg= + + + +qJNgvYmC92FBcdgXqV7u0TCywBwAoRSW9VsYpluHc2M= + + + +OC1v8JI2m0cBR8OfzterqamyGoAjGLRsxpOkjauhnP4= + + + +eTu9vGNI+Pn8KBq8Tf4VPNaGJ7srNIKI/Mf/WUAUFos= + + + +IvH2+9a4Np0dtDtyI/eOLn9bT/MYuzmdNjWTs2GQOTM= + + + ++s7/n9aNRdSSP5C9yjgGW0DK5+WAZj1GZzaT8FtZRWc= + + + +GGKaRP+Ko/HHOQHZbsp7JinOllLn/fWiWciLpxCTzVA= + + + +ezbad2I+Qvqq+HBtFj5fCR73B7u1OpaGp8LbvJiksx8= + + + +i6payEnlv8vWHucju//UBssYNzmr3SES3nSuWDuJU6c= + + + +wIF0gTMZq7piBZ9E/+l5UgyW9pSywlBedaETJbK5euE= + + + +MZ/WXjPf0eoYefs4grYwm+dI4ehPs1D8ElGWv+wLcGk= + + + +iEpyiqHYkrP3GQpul3S6C87yG+PyVBWzhrmvxzPf//0= + + + +JmKqm+7nSe0dUkYRduPMoyyfc+Rg9gTVg2J7/pJq5F4= + + + +0uNZLiaoKNgqjrh+2LAZIq6GUFG3lGJHf9PXHbmMeM0= + + + +vo610xtMs+DvFmvUgCyleSB4MGxZ3pM/RWtx8uxTSg0= + + + +BKVmFH1pFROxq/f6oH1cJ9PJeaxLGoKNF9DzThgzoCU= + + + +fvhUuEK9zAM1W8f0Z3gXWnMZO03A8Ae4CxvDcriiJhs= + + + +V1Ke5HdTcgx64WAnHV4oMLzOMyVITvvc9cTLNAL3jPY= + + + +2QJRn/uDj8ZrhFi6DGeIxKFFVluoZRr7MHAdqKePB74= + + + +4YF4AebfNXlSv1SNQGuuonzGh/f0LUj5e1DZfIWPKZk= + + + +f6LUaSBsZsugcSX2A3xcaHs+rghK2nFAneqNp7WbOMw= + + + +fl7TG+5Mgf/y6KR8TdSEPcC/teaTrmRLm+lr9TSpOyg= + + + +AK4h31Wy4eet4Z4+uT8uMy2Kgcyauiy2tvNj/MjQGKA= + + + +pQDmuQlk3a/Nm4n5cfrpBSNmVVZNF0Ap+Mn7WztAzV8= + + + +6MewTgCyy7oMiEvq3KkLoNcPwf7TXOAoEtF14XSlYoM= + + + +TQv9HMruSObr1t6FPaOldowK6px+UQ67FpnnFOc+u3o= + + + +2fQmP+BKfZBT88yUwA6wwyE62PR6Q2F7wWEi+kkzHvM= + + + +kc1wJhzruwjgLS1vfPkmSQHz67R48K6bh8LpwjDuE+M= + + + +qkbuZm3KXZNobT9lxfhl6LdiG8ERnIdYMArm45mDI3g= + + + +DMHAhBUsbboDCd5y8Q83zu3fnJyfF1QmmQzvP7Fjf1U= + + + +al6tluFLVe5845VesK7QLEaHmZygqysNWzZOilZUne4= + + + +xArlQu0AmI/ocXV+KTaJc6gg08S6LvGW+qjIpPRXCOg= + + + +et3cgWnyqfSwP3HokpLXqZug3lwDT7lNt3p0JCrxuBE= + + + +xg0blL/qm/PlWrv4gh9D5VbFduHT4mEgGpNRugTHpbo= + + + +e4K/fWxgP0RPYt1CcPLMU+b0DwOS4fxsN+NcVcOmPKc= + + + +NWtT1Dj4doBFgGMhL/7RjdD1FG12IcjqwujPGK1PAmY= + + + +Q0GXfa+7G5Om0sog4aOZZWvd/1ahRg4KyRF6UM2Fnb4= + + + +0YYITXDSonw21JyxtiJjBf0qFaE0xtQ3RNVNF4qu0rk= + + + +Q0GXfa+7G5Om0sog4aOZZWvd/1ahRg4KyRF6UM2Fnb4= + + + +N06Ed3Ihxxo2MldtVQKRq0qYl1eRbuo0zEe2medE1J0= + + + +ph0ljcHGNXlJO1dPAiAQgDpRH3+mbMZZ3Ega7lNW/cw= + + + +3i13zlE5UUPepY9sbRmsu3oxYmIBCpM9hwL0YyiVaEI= + + + +BzK0UNVeSZCohwIBfIkqCru85HE/86q/2MZUEQJWcoU= + + + +qFC8fOl6Tg7LrLrT62RSQLKSr3GP6O2UwjnG9s7DAck= + + + +ddihq2E6gV6+g48CtlRBvDFnKx97KWUJxoNr8SQ7sUQ= + + + +elPeRLEot+b5h79G5sudnUNkc6GhAyye82uL2cn9xxc= + + + +SYKDGM5OUHfYf65YinTD2vzNNMNUjKEABJaxy/68hv4= + + + +nwK+t+eTDiAjgXRMhNpbO52ccNtX1T8to7iUEfvtEw0= + + + +JXfJFsytb7Ca9bHn2mZ7J5UVde6gr5WIQWFARUEbSAI= + + + +Wv4xA+PTooZ5isNMnSm/xQCDNSoMc7W/rUG6m60NEFE= + + + +GE8gsheB4mDn5m7Ef0c0pXefYNoFeybCN6X+IvpJmns= + + + +BdM1nG2OXAGZ/Jjerne40hr4z+CUVQDaGGDC9NKHbJ4= + + + +G1lZylmCqAW+9pxtAqkRcvWmAaqrhmKmFNQ6S+Gfofo= + + + +EmiQ9zOZtFKGu1XIfrFwbGkJApV30yvGAKx9MLuRjdc= + + + +qNnHgB7/8DElpHmMab0YAkGsf15SmvWO52AwSD4brog= + + + +7GQe3ejeCKtt6U+41MqwJ8q3SaU7cwmfF1sFnFygjCw= + + + +xUE9SahpNHNokTSvhfV04BMB0Mdz4wsHy22HYi+93B4= + + + +50M0LcqxGYrsu0BeMq13DAptfhO6NKmsqRFCnAxFajs= + + + +Ds+e1ERJ7fKikfUF7LTSOFWaGS08JNA/R1xFTPfyGxY= + + + +1k/BcizBicjmBjDJEJiy84ZPGZRaIAD1I3IHXAm1igo= + + + +3zbo3CppqPwVpA3f2f9lDsBs06dPHIwlyuqx6pOxOyE= + + + +hc3VpxyhSp3r9EwGpe+A4bbpaOdj9GUyBOPdj80hGTE= + + + +47LZQG7H45gvdxyu7YNh84m0mqz+lHzw1v3C4y1HKNQ= + + + +spXDbatBHTzSFR4N03w93hLrVybuw/XRjZAfneskshI= + + + +l7tDb2GpWcNj93JU9VgQbbDgqEtDA7bdufKK12uMkPs= + + + +qoH8usHbcfbB5f0cjjdPuVBueHof14iBqeXKMIQEMRk= + + + +VFg8qmBikNCdPflDeephVt91R7aPsIIXP9Ra6vjB89w= + + + +TphpTsohWyWK9j+2DXpOyvJSydRMq2Av5B0otjA9f9I= + + + +vqXi7pK9urSnMFrdVlcc8IFvLcUduAMNIIgbnFvQc4A= + + + +HQr3u9+djLy7CqBURR3qMnfvWxQRk+7KsyluA9KadzQ= + + + +PTz1Q9tb2jmV/h/NF704nw/+wpelkdig4FdwBoTkXIs= + + + +3mjrv/wd1wI4zGsbemLKWEA3ARYZyZF/lx8XLE87aSQ= + + + +mmYqlg9ixS5F00DUDkRiD8LJARxEPw7FwZpsINr4mwU= + + + +h2ItAbWbIgB1+7uU3+RtKHSa7G1cCnNqLu+ENHLGUKQ= + + + +inphL/shFiOknwr0eLnUc7CGLICqacQ36GidvhaEuJ4= + + + +C+hHn6OO+njwAy8GimgFpo+Owigwarpd0+/LdckPdjE= + + + +EwF0JIrEoFCTVRThWK/aNHTfqUzl+oQ5xaoEVeCKakQ= + + + +LBBYbeRMyt+BmcaK6OFcYxXCHatfcBy3WZmMEyeD494= + + + +LrlCUI8SKgQ/Ec+nZhhCBp5rUaVN4zQUfmLeISzlj5Y= + + + +7wNkYofR/ltKHZS5M5XO6KRfaOFY/RLoRk/60QGhqpg= + + + +qJNgvYmC92FBcdgXqV7u0TCywBwAoRSW9VsYpluHc2M= + + + +OC1v8JI2m0cBR8OfzterqamyGoAjGLRsxpOkjauhnP4= + + + +eTu9vGNI+Pn8KBq8Tf4VPNaGJ7srNIKI/Mf/WUAUFos= + + + +IvH2+9a4Np0dtDtyI/eOLn9bT/MYuzmdNjWTs2GQOTM= + + + ++s7/n9aNRdSSP5C9yjgGW0DK5+WAZj1GZzaT8FtZRWc= + + + +GGKaRP+Ko/HHOQHZbsp7JinOllLn/fWiWciLpxCTzVA= + + + +ezbad2I+Qvqq+HBtFj5fCR73B7u1OpaGp8LbvJiksx8= + + + +i6payEnlv8vWHucju//UBssYNzmr3SES3nSuWDuJU6c= + + + +wIF0gTMZq7piBZ9E/+l5UgyW9pSywlBedaETJbK5euE= + + + +MZ/WXjPf0eoYefs4grYwm+dI4ehPs1D8ElGWv+wLcGk= + + + +iEpyiqHYkrP3GQpul3S6C87yG+PyVBWzhrmvxzPf//0= + + + +JmKqm+7nSe0dUkYRduPMoyyfc+Rg9gTVg2J7/pJq5F4= + + + +0uNZLiaoKNgqjrh+2LAZIq6GUFG3lGJHf9PXHbmMeM0= + + + +vo610xtMs+DvFmvUgCyleSB4MGxZ3pM/RWtx8uxTSg0= + + + +BKVmFH1pFROxq/f6oH1cJ9PJeaxLGoKNF9DzThgzoCU= + + + +fvhUuEK9zAM1W8f0Z3gXWnMZO03A8Ae4CxvDcriiJhs= + + + +V1Ke5HdTcgx64WAnHV4oMLzOMyVITvvc9cTLNAL3jPY= + + + +2QJRn/uDj8ZrhFi6DGeIxKFFVluoZRr7MHAdqKePB74= + + + +4YF4AebfNXlSv1SNQGuuonzGh/f0LUj5e1DZfIWPKZk= + + + +f6LUaSBsZsugcSX2A3xcaHs+rghK2nFAneqNp7WbOMw= + + + +fl7TG+5Mgf/y6KR8TdSEPcC/teaTrmRLm+lr9TSpOyg= + + + +AK4h31Wy4eet4Z4+uT8uMy2Kgcyauiy2tvNj/MjQGKA= + + + +pQDmuQlk3a/Nm4n5cfrpBSNmVVZNF0Ap+Mn7WztAzV8= + + + +6MewTgCyy7oMiEvq3KkLoNcPwf7TXOAoEtF14XSlYoM= + + + +ZrrAANVs6eFlgvPrH3jphJOB28iXV5DYoTvAGUkrSIM= + + + +FpJOVzu7q+uz3NnFYKiPfwqv2G+aVTIzklNKJX6riCM= + + + +8UBgJDPoA+ercTDxCbcF1z/d7l27vivl/3DuTozfkKw= + + + +QYmeaCeMCYrafGbrGuJAZtNPkT9TuBvLzB/xpouHd7o= + + + +Mnd6d8RqbVFwyKwNLKiziXqdDKtay+wmEj79r2jWL+I= + + + +B9TJ5UITKG2e98hvLldn0ns2ERy/JXEX3lgNB2VpWsM= + + + ++XRJngMx0882lgPV5wZGiLeMxxg7irSEaXWfsiI5EIk= + + + +M9rjopRK33OYpomKSBskxCswnJ6xk2Bwv5X/MLm1INY= + + + +kc1wJhzruwjgLS1vfPkmSQHz67R48K6bh8LpwjDuE+M= + + + +qkbuZm3KXZNobT9lxfhl6LdiG8ERnIdYMArm45mDI3g= + + + +DMHAhBUsbboDCd5y8Q83zu3fnJyfF1QmmQzvP7Fjf1U= + + + +al6tluFLVe5845VesK7QLEaHmZygqysNWzZOilZUne4= + + + +xArlQu0AmI/ocXV+KTaJc6gg08S6LvGW+qjIpPRXCOg= + + + +et3cgWnyqfSwP3HokpLXqZug3lwDT7lNt3p0JCrxuBE= + + + +xg0blL/qm/PlWrv4gh9D5VbFduHT4mEgGpNRugTHpbo= + + + +e4K/fWxgP0RPYt1CcPLMU+b0DwOS4fxsN+NcVcOmPKc= + + + +NWtT1Dj4doBFgGMhL/7RjdD1FG12IcjqwujPGK1PAmY= + + + +Q0GXfa+7G5Om0sog4aOZZWvd/1ahRg4KyRF6UM2Fnb4= + + + +0YYITXDSonw21JyxtiJjBf0qFaE0xtQ3RNVNF4qu0rk= + + + +Q0GXfa+7G5Om0sog4aOZZWvd/1ahRg4KyRF6UM2Fnb4= + + + +N06Ed3Ihxxo2MldtVQKRq0qYl1eRbuo0zEe2medE1J0= + + + +ph0ljcHGNXlJO1dPAiAQgDpRH3+mbMZZ3Ega7lNW/cw= + + + +3i13zlE5UUPepY9sbRmsu3oxYmIBCpM9hwL0YyiVaEI= + + + +BzK0UNVeSZCohwIBfIkqCru85HE/86q/2MZUEQJWcoU= + + + +qFC8fOl6Tg7LrLrT62RSQLKSr3GP6O2UwjnG9s7DAck= + + + +ddihq2E6gV6+g48CtlRBvDFnKx97KWUJxoNr8SQ7sUQ= + + + +elPeRLEot+b5h79G5sudnUNkc6GhAyye82uL2cn9xxc= + + + +SYKDGM5OUHfYf65YinTD2vzNNMNUjKEABJaxy/68hv4= + + + +nwK+t+eTDiAjgXRMhNpbO52ccNtX1T8to7iUEfvtEw0= + + + +JXfJFsytb7Ca9bHn2mZ7J5UVde6gr5WIQWFARUEbSAI= + + + +Wv4xA+PTooZ5isNMnSm/xQCDNSoMc7W/rUG6m60NEFE= + + + +GE8gsheB4mDn5m7Ef0c0pXefYNoFeybCN6X+IvpJmns= + + + +BdM1nG2OXAGZ/Jjerne40hr4z+CUVQDaGGDC9NKHbJ4= + + + +G1lZylmCqAW+9pxtAqkRcvWmAaqrhmKmFNQ6S+Gfofo= + + + +EmiQ9zOZtFKGu1XIfrFwbGkJApV30yvGAKx9MLuRjdc= + + + +qNnHgB7/8DElpHmMab0YAkGsf15SmvWO52AwSD4brog= + + + +7GQe3ejeCKtt6U+41MqwJ8q3SaU7cwmfF1sFnFygjCw= + + + +xUE9SahpNHNokTSvhfV04BMB0Mdz4wsHy22HYi+93B4= + + + +50M0LcqxGYrsu0BeMq13DAptfhO6NKmsqRFCnAxFajs= + + + +Ds+e1ERJ7fKikfUF7LTSOFWaGS08JNA/R1xFTPfyGxY= + + + +1k/BcizBicjmBjDJEJiy84ZPGZRaIAD1I3IHXAm1igo= + + + +3zbo3CppqPwVpA3f2f9lDsBs06dPHIwlyuqx6pOxOyE= + + + +hc3VpxyhSp3r9EwGpe+A4bbpaOdj9GUyBOPdj80hGTE= + + + +47LZQG7H45gvdxyu7YNh84m0mqz+lHzw1v3C4y1HKNQ= + + + +spXDbatBHTzSFR4N03w93hLrVybuw/XRjZAfneskshI= + + + +l7tDb2GpWcNj93JU9VgQbbDgqEtDA7bdufKK12uMkPs= + + + +qoH8usHbcfbB5f0cjjdPuVBueHof14iBqeXKMIQEMRk= + + + +VFg8qmBikNCdPflDeephVt91R7aPsIIXP9Ra6vjB89w= + + + +TphpTsohWyWK9j+2DXpOyvJSydRMq2Av5B0otjA9f9I= + + + +vqXi7pK9urSnMFrdVlcc8IFvLcUduAMNIIgbnFvQc4A= + + + +HQr3u9+djLy7CqBURR3qMnfvWxQRk+7KsyluA9KadzQ= + + + +PTz1Q9tb2jmV/h/NF704nw/+wpelkdig4FdwBoTkXIs= + + + +3mjrv/wd1wI4zGsbemLKWEA3ARYZyZF/lx8XLE87aSQ= + + + +mmYqlg9ixS5F00DUDkRiD8LJARxEPw7FwZpsINr4mwU= + + + +h2ItAbWbIgB1+7uU3+RtKHSa7G1cCnNqLu+ENHLGUKQ= + + + +inphL/shFiOknwr0eLnUc7CGLICqacQ36GidvhaEuJ4= + + + +C+hHn6OO+njwAy8GimgFpo+Owigwarpd0+/LdckPdjE= + + + +EwF0JIrEoFCTVRThWK/aNHTfqUzl+oQ5xaoEVeCKakQ= + + + +LBBYbeRMyt+BmcaK6OFcYxXCHatfcBy3WZmMEyeD494= + + + +LrlCUI8SKgQ/Ec+nZhhCBp5rUaVN4zQUfmLeISzlj5Y= + + + +7wNkYofR/ltKHZS5M5XO6KRfaOFY/RLoRk/60QGhqpg= + + + +qJNgvYmC92FBcdgXqV7u0TCywBwAoRSW9VsYpluHc2M= + + + +OC1v8JI2m0cBR8OfzterqamyGoAjGLRsxpOkjauhnP4= + + + +eTu9vGNI+Pn8KBq8Tf4VPNaGJ7srNIKI/Mf/WUAUFos= + + + +IvH2+9a4Np0dtDtyI/eOLn9bT/MYuzmdNjWTs2GQOTM= + + + ++s7/n9aNRdSSP5C9yjgGW0DK5+WAZj1GZzaT8FtZRWc= + + + +GGKaRP+Ko/HHOQHZbsp7JinOllLn/fWiWciLpxCTzVA= + + + +ezbad2I+Qvqq+HBtFj5fCR73B7u1OpaGp8LbvJiksx8= + + + +i6payEnlv8vWHucju//UBssYNzmr3SES3nSuWDuJU6c= + + + +wIF0gTMZq7piBZ9E/+l5UgyW9pSywlBedaETJbK5euE= + + + +MZ/WXjPf0eoYefs4grYwm+dI4ehPs1D8ElGWv+wLcGk= + + + +iEpyiqHYkrP3GQpul3S6C87yG+PyVBWzhrmvxzPf//0= + + + +JmKqm+7nSe0dUkYRduPMoyyfc+Rg9gTVg2J7/pJq5F4= + + + +0uNZLiaoKNgqjrh+2LAZIq6GUFG3lGJHf9PXHbmMeM0= + + + +vo610xtMs+DvFmvUgCyleSB4MGxZ3pM/RWtx8uxTSg0= + + + +BKVmFH1pFROxq/f6oH1cJ9PJeaxLGoKNF9DzThgzoCU= + + + +fvhUuEK9zAM1W8f0Z3gXWnMZO03A8Ae4CxvDcriiJhs= + + + +V1Ke5HdTcgx64WAnHV4oMLzOMyVITvvc9cTLNAL3jPY= + + + +2QJRn/uDj8ZrhFi6DGeIxKFFVluoZRr7MHAdqKePB74= + + + +4YF4AebfNXlSv1SNQGuuonzGh/f0LUj5e1DZfIWPKZk= + + + +f6LUaSBsZsugcSX2A3xcaHs+rghK2nFAneqNp7WbOMw= + + + +fl7TG+5Mgf/y6KR8TdSEPcC/teaTrmRLm+lr9TSpOyg= + + + +AK4h31Wy4eet4Z4+uT8uMy2Kgcyauiy2tvNj/MjQGKA= + + + +pQDmuQlk3a/Nm4n5cfrpBSNmVVZNF0Ap+Mn7WztAzV8= + + + +6MewTgCyy7oMiEvq3KkLoNcPwf7TXOAoEtF14XSlYoM= + + + +ZrrAANVs6eFlgvPrH3jphJOB28iXV5DYoTvAGUkrSIM= + + + +FpJOVzu7q+uz3NnFYKiPfwqv2G+aVTIzklNKJX6riCM= + + + +8UBgJDPoA+ercTDxCbcF1z/d7l27vivl/3DuTozfkKw= + + + +QYmeaCeMCYrafGbrGuJAZtNPkT9TuBvLzB/xpouHd7o= + + + +Mnd6d8RqbVFwyKwNLKiziXqdDKtay+wmEj79r2jWL+I= + + + +B9TJ5UITKG2e98hvLldn0ns2ERy/JXEX3lgNB2VpWsM= + + + ++XRJngMx0882lgPV5wZGiLeMxxg7irSEaXWfsiI5EIk= + + + +M9rjopRK33OYpomKSBskxCswnJ6xk2Bwv5X/MLm1INY= + + + + + + +u/jU3U4Zm5ihTMSjKGlGYbWzDfRkGphPPHx3gJIYEJ4= + + + +KLP5Yj4yR1iyl/D1PXJtTtFLXiHPmXi4Rg/YNKDwAr/3zeI1cRUNkZ/fN5B7Sov2gm397YVmwRBE +LK7I7/LlJXa3ggIUkFzj5FJT30M6gxeY8oJTBsHkO0LYPrIAwurwmM0sTE1IJVjrCtxgBySxPo0C +Uf0WNbaqlHxwC5sMIgWT2a3/JuiReMF7rBPs44TQpn94x0p9OiD2PVlOb684zUH3Jc3iPN1FxwP/ +8ToqIOGheu17XayFQTQLyUUOYTkLf+6RQzmZUDALQZn8upb4uMONjWCjsuctkqkSEURLtEnHyozy +yYgL6DTXADGeMnyDWtzWYq1CrC7gB1U6cD0VEQ== + + + + +MIIErTCCA5WgAwIBAgIBZjANBgkqhkiG9w0BAQsFADCBnzELMAkGA1UEBhMCS1IxFDASBgNVBAgM +C1NvdXRoIEtvcmVhMQ4wDAYDVQQHDAVTdXdvbjEmMCQGA1UECgwdU2Ftc3VuZyBFbGVjdHJvbmlj +cyBDby4sIEx0ZC4xDzANBgNVBAsMBk1vYmlsZTExMC8GA1UEAwwoU2Ftc3VuZyBUaXplbiBERVZF +TE9QRVIgUGFydG5lciBDQSBDbGFzczAeFw0xOTA0MDYyMzE4MTJaFw0yMDA0MDUyMzE4MTJaMIIB +ODERMA8GA1UEAwwIVGl6ZW5TREsxCTAHBgNVBAsMADEJMAcGA1UECgwAMQkwBwYDVQQHDAAxCTAH +BgNVBAgMADEJMAcGA1UEBhMAMYHrMIHoBgkqhkiG9w0BCQEWgdpsb3JkYXNhcmlsQGdvb2dsZW1h +aWwuY29tJnN0YXRlPWFjY291bnRjaGVja2RvZ2VuZXJhdGVkc3RhdGV0ZXh0JnRva2VuX3R5cGU9 +YmVhcmVyJmNsb3NlZEFjdGlvbj1zaWduSW5TdWNjZXNzJnVzZXJJZD1qYnF6eHpmbDQyJmFjY2Vz +c190b2tlbl9leHBpcmVzX2luPTg2Mzk5MyZjbGllbnRfaWQ9NGZiN2ZuZjNucCZhcGlfc2VydmVy +X3VybD11cy1hdXRoMi5zYW1zdW5nb3NwLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAMVByL5ttMOykSLJ/kSxg5htKf4S7CzysjOCO7zDsw+/w7z2IbTtnd5hGYQqtmzSbUApzp7I +f7Z0+nUgtSM8kldWhVss9ieLZRzrN/PprjMwOIl862hZhvhr5MOHwGNyChAEg1KhYDPr+rMTNOux +yqVYT8wKY4qKfEu7mmo2kkEAAsI9fUHRRt2k0yujT5tYcUm4cEcewrgQw4nMVIJfDq9Fs9RVFGvY +gO9YXYgZxVNJWaHcMwmxK49su4l8FbLOpbqaVoWwkGnIyOu3QNeiK90ttKPKdzNqqsa8pNwExSgz +ROq6E9/SlkqWcDVeptKaM60TfnqbmSVez55tx6v1gikCAwEAAaNYMFYwVAYDVR0RBE0wS4YUVVJO +OnRpemVuOnBhY2thZ2VpZD2GM1VSTjp0aXplbjpkZXZpY2VpZD0yLjAjWmVBazZiRkdJTDBtUXly +bDN3WkZVTGJsakI4PTANBgkqhkiG9w0BAQsFAAOCAQEA0tpFQRyfNKmDmG027sUFLsScwhlV7yl8 +E3NZYtRjmBni8cvaHDGH7d61J+0PCVzz/+WPa2fwvev5WjD/EhoXM1gFZJBHE7Br4lT2OoXMZNm8 +xgqWd0VUMTyC3I9Ds40kIfPkUuHroZ5f0WDqmiFrK9tZVceUVgOOJ6tF1AUhM/bGS8PKK9CE7yFm +Plz9fv3te0z9a8SYticGkmvyMeBqVfHCucsGYrnVoPS+dqO7ExAxnnqHn/fZguQ5IdOQrYMDFhlA +PG6UBV9aRnFw8NloxQ0OCozeAkayyFTj0dwf98qlWCnoQA4/WEzlLQvDy4C6wAUy7puVbM99ruCe +d4tuCw== + + +MIIDvTCCAqWgAwIBAgICfoQwDQYJKoZIhvcNAQELBQAwgZAxCzAJBgNVBAYTAktSMRQwEgYDVQQI +DAtTb3V0aCBLb3JlYTEOMAwGA1UEBwwFU3V3b24xJjAkBgNVBAoMHVNhbXN1bmcgRWxlY3Ryb25p +Y3MgQ28uLCBMdGQuMTMwMQYDVQQDDCpTYW1zdW5nIFRpemVuIERFVkVMT1BFUiBQYXJ0bmVyIFJv +b3QgQ2xhc3MwHhcNMTMxMjMwMTUwMTQ3WhcNMjgxMjI2MTUwMTQ3WjCBnzELMAkGA1UEBhMCS1Ix +FDASBgNVBAgMC1NvdXRoIEtvcmVhMQ4wDAYDVQQHDAVTdXdvbjEmMCQGA1UECgwdU2Ftc3VuZyBF +bGVjdHJvbmljcyBDby4sIEx0ZC4xDzANBgNVBAsMBk1vYmlsZTExMC8GA1UEAwwoU2Ftc3VuZyBU +aXplbiBERVZFTE9QRVIgUGFydG5lciBDQSBDbGFzczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBANZ4SYoV1OV+TbLkMqyp3cbLmtsxzGNBI5Vp9YllLBIqVeQH3652YXZOoFE5StKV4VTF +FPZ81KpUoN55Yey1d/XOizkoMV30SIgAycCjsiVQ1nldQtcW+LvwItguMCkdIthSuZ75wmtZsc3Z +4dwdWTEFMlS6nYGX8oMj0clj8c4DAv6eZJBn3R7tY4bRL1Lj6umCDgOQMlRKlCnQLzfZJIY3UJY8 +Z8EC7VWu2MR+ZeQwtTRgiYr9Cs43R6MPV4UGuqqsrYpErvetm6q4R+ZLfRG9/sMs0A2vpAZw6mkR +CP2hG8JM4HFwkGJV3pjP/bEwp0VQHx+t+myXk3q7hkcZRU8CAwEAAaMQMA4wDAYDVR0TBAUwAwEB +/zANBgkqhkiG9w0BAQsFAAOCAQEAdEUQlqiFPLGxbHCv2eiF4gZgVo5cjHSuLuK2BNXjlBD639Or +oB+xb3AH5Na592rhiZaQ5MFuWZAN61NfI0CVic+sNxX92suKTAMFU9XsHSveirx5Z8ettJMaJHzL +I71E1CFdDJBzBChnrJ+BBeW3QTenRlSYUs5kXWjhJWQGfQwlNAmnft/JdBVDY/V+oumtT19VCj04 +fACl0zRuybvSXNzO3tP1aPzbtpfnUye5tYzuzP5qYadtVjJU4pnUVVo25iD+qOQG4DTHxMYS+3MW +ACK+EaXMpIOJxQguK/jeMJXGxoKlebVgYuUEKFwtgbJ5hy/ss5epcfyrRjurvAY/VA== + + + + + \ No newline at end of file diff --git a/.tproject b/.tproject new file mode 100644 index 0000000..7ca3e1f --- /dev/null +++ b/.tproject @@ -0,0 +1,11 @@ + + + + + wearable-4.0 + + + + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/config.xml b/config.xml new file mode 100644 index 0000000..c4eaeac --- /dev/null +++ b/config.xml @@ -0,0 +1,12 @@ + + + + + + + + Openhab2 + + + + diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..c293e59 --- /dev/null +++ b/css/style.css @@ -0,0 +1,3 @@ +/* this file is left blank on purpose, if developer can't find classes he needs in framework, he can customize his +app adding his own styles here + */ \ No newline at end of file diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..1f02568 Binary files /dev/null and b/icon.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..571ea1e --- /dev/null +++ b/index.html @@ -0,0 +1,25 @@ + + + + + Basic + + + + + + +
+
+

TAU Basic

+
+
+

Hello!

+
+
+ + + + + + diff --git a/js/app.js b/js/app.js new file mode 100644 index 0000000..c984507 --- /dev/null +++ b/js/app.js @@ -0,0 +1,77 @@ +(function() { + window.addEventListener("tizenhwkey", function(ev) { + var activePopup = null, page = null, pageId = ""; + + if (ev.keyName === "back") { + activePopup = document.querySelector(".ui-popup-active"); + page = document.getElementsByClassName("ui-page-active")[0]; + pageId = page ? page.id : ""; + + if (pageId === "main" && !activePopup) { + try { + tizen.application.getCurrentApplication().exit(); + } catch (ignore) { + } + } else { + window.history.back(); + } + } + }); +}()); + +function parseSitemap(sitemap) { + var output = parseWidgets(sitemap.homepage.widgets); + document.getElementById("main_text").innerHTML = output; +} + +function parseWidgets(widgets) { + var s=""; + widgets.forEach( function(wdg) { + switch (wdg.type) { + case "Frame": + s+="

"+wdg.label+"
"; + if (("widgets" in wdg) && wdg.widgets.length >0){ + s+=parseWidgets(wdg.widgets); + } + s+="

"; + break; + + case "Switch": + break; + case "Slider": + var value=0; + if (wdg.item.state !== null){value = wdg.item.state;} + s+=createElement(wdg.label, wdg.item.name,''); + break; + case "Selection": + s+="Selection
"; + break; + case "Text": + s+=createElement(wdg.label,'',''); + break; + + default: + s+="Other
"; + break; + } + + }); + return s; +} + +function createElement(label, id, inner){ + return '
'+label+''+inner+'
'; +} + + +function loadgui() { + var client = new XMLHttpRequest(); + client.onreadystatechange = function() { + if (this.readyState === 4 && this.status === 200) { + var myArr = JSON.parse(this.responseText); + parseSitemap(myArr); + } + }; + client.open('GET', 'http://habctrl:8080/rest/sitemaps/default', true); + client.send(); +} \ No newline at end of file diff --git a/js/circle-helper.js b/js/circle-helper.js new file mode 100644 index 0000000..3e45c59 --- /dev/null +++ b/js/circle-helper.js @@ -0,0 +1,28 @@ +/*global tau */ +/*jslint unparam: true */ +(function (tau) { + + // This logic works only on circular device. + if (tau.support.shape.circle) { + /** + * pagebeforeshow event handler + * Do preparatory works and adds event listeners + */ + document.addEventListener("pagebeforeshow", function (event) { + /** + * page - Active page element + * list - NodeList object for lists in the page + */ + var page, + list; + + page = event.target; + if (page.id !== "page-snaplistview" && page.id !== "page-swipelist" && page.id !== "page-marquee-list") { + list = page.querySelector(".ui-listview"); + if (list) { + tau.widget.ArcListview(list); + } + } + }); + } +}(tau)); diff --git a/js/lowBatteryCheck.js b/js/lowBatteryCheck.js new file mode 100644 index 0000000..eb0e26f --- /dev/null +++ b/js/lowBatteryCheck.js @@ -0,0 +1,64 @@ +(function () { + var systeminfo = { + + systeminfo: null, + + lowThreshold: 0.04, + + listenBatteryLowState: function () { + var self = this; + + try { + this.systeminfo.addPropertyValueChangeListener( + "BATTERY", + function change(battery) { + if (!battery.isCharging) { + try { + tizen.application.getCurrentApplication().exit(); + } catch (ignore) { + } + } + }, + { + lowThreshold: self.lowThreshold + }, + function onError(error) { + console.warn("An error occurred " + error.message); + } + ); + } catch (ignore) { + } + }, + + checkBatteryLowState: function () { + var self = this; + + try { + this.systeminfo.getPropertyValue( + "BATTERY", + function (battery) { + if (battery.level < self.lowThreshold && !battery.isCharging) { + try { + tizen.application.getCurrentApplication().exit(); + } catch (ignore) { + } + } + }, + null); + } catch (ignore) { + } + }, + + init: function () { + if (typeof tizen === "object" && typeof tizen.systeminfo === "object") { + this.systeminfo = tizen.systeminfo; + this.checkBatteryLowState(); + this.listenBatteryLowState(); + } else { + console.warn("tizen.systeminfo is not available."); + } + } + }; + + systeminfo.init(); +}()); diff --git a/lib/tau/LICENSE.Flora b/lib/tau/LICENSE.Flora new file mode 100644 index 0000000..4ab7e53 --- /dev/null +++ b/lib/tau/LICENSE.Flora @@ -0,0 +1,206 @@ +Flora License + +Version 1.1, April, 2013 + +http://floralicense.org/license/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, +and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by +the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and +all other entities that control, are controlled by, or are +under common control with that entity. For the purposes of +this definition, "control" means (i) the power, direct or indirect, +to cause the direction or management of such entity, +whether by contract or otherwise, or (ii) ownership of fifty percent (50%) +or more of the outstanding shares, or (iii) beneficial ownership of +such entity. + +"You" (or "Your") shall mean an individual or Legal Entity +exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation source, +and configuration files. + +"Object" form shall mean any form resulting from mechanical +transformation or translation of a Source form, including but +not limited to compiled object code, generated documentation, +and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, +made available under the License, as indicated by a copyright notice +that is included in or attached to the work (an example is provided +in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, +that is based on (or derived from) the Work and for which the editorial +revisions, annotations, elaborations, or other modifications represent, +as a whole, an original work of authorship. For the purposes of this License, +Derivative Works shall not include works that remain separable from, +or merely link (or bind by name) to the interfaces of, the Work and +Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original +version of the Work and any modifications or additions to that Work or +Derivative Works thereof, that is intentionally submitted to Licensor +for inclusion in the Work by the copyright owner or by an individual or +Legal Entity authorized to submit on behalf of the copyright owner. +For the purposes of this definition, "submitted" means any form of +electronic, verbal, or written communication sent to the Licensor or +its representatives, including but not limited to communication on +electronic mailing lists, source code control systems, and issue +tracking systems that are managed by, or on behalf of, the Licensor +for the purpose of discussing and improving the Work, but excluding +communication that is conspicuously marked or otherwise designated +in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity +on behalf of whom a Contribution has been received by Licensor and +subsequently incorporated within the Work. + +"Tizen Certified Platform" shall mean a software platform that complies +with the standards set forth in the Tizen Compliance Specification +and passes the Tizen Compliance Tests as defined from time to time +by the Tizen Technical Steering Group and certified by the Tizen +Association or its designated agent. + +2. Grant of Copyright License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the +Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +(except as stated in this section) patent license to make, have made, +use, offer to sell, sell, import, and otherwise transfer the Work +solely as incorporated into a Tizen Certified Platform, where such +license applies only to those patent claims licensable by such +Contributor that are necessarily infringed by their Contribution(s) +alone or by combination of their Contribution(s) with the Work solely +as incorporated into a Tizen Certified Platform to which such +Contribution(s) was submitted. If You institute patent litigation +against any entity (including a cross-claim or counterclaim +in a lawsuit) alleging that the Work or a Contribution incorporated +within the Work constitutes direct or contributory patent infringement, +then any patent licenses granted to You under this License for that +Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the +Work or Derivative Works thereof pursuant to the copyright license +above, in any medium, with or without modifications, and in Source or +Object form, provided that You meet the following conditions: + + 1. You must give any other recipients of the Work or Derivative Works + a copy of this License; and + 2. You must cause any modified files to carry prominent notices stating + that You changed the files; and + 3. You must retain, in the Source form of any Derivative Works that + You distribute, all copyright, patent, trademark, and attribution + notices from the Source form of the Work, excluding those notices + that do not pertain to any part of the Derivative Works; and + 4. If the Work includes a "NOTICE" text file as part of its distribution, + then any Derivative Works that You distribute must include a readable + copy of the attribution notices contained within such NOTICE file, + excluding those notices that do not pertain to any part of + the Derivative Works, in at least one of the following places: + within a NOTICE text file distributed as part of the Derivative Works; + within the Source form or documentation, if provided along with the + Derivative Works; or, within a display generated by the Derivative Works, + if and wherever such third-party notices normally appear. + The contents of the NOTICE file are for informational purposes only + and do not modify the License. You may add Your own attribution notices + within Derivative Works that You distribute, alongside or as an addendum + to the NOTICE text from the Work, provided that such additional attribution + notices cannot be construed as modifying the License. You may add Your own + copyright statement to Your modifications and may provide additional or + different license terms and conditions for use, reproduction, or + distribution of Your modifications, or for any such Derivative Works + as a whole, provided Your use, reproduction, and distribution of + the Work otherwise complies with the conditions stated in this License + and your own copyright statement or terms and conditions do not conflict + the conditions stated in the License including section 3. + +5. Submission of Contributions. Unless You explicitly state otherwise, +any Contribution intentionally submitted for inclusion in the Work +by You to the Licensor shall be under the terms and conditions of +this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify +the terms of any separate license agreement you may have executed +with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade +names, trademarks, service marks, or product names of the Licensor, +except as required for reasonable and customary use in describing the +origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or +agreed to in writing, Licensor provides the Work (and each +Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied, including, without limitation, any warranties or conditions +of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +PARTICULAR PURPOSE. You are solely responsible for determining the +appropriateness of using or redistributing the Work and assume any +risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, +whether in tort (including negligence), contract, or otherwise, +unless required by applicable law (such as deliberate and grossly +negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, +incidental, or consequential damages of any character arising as a +result of this License or out of the use or inability to use the +Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all +other commercial damages or losses), even if such Contributor +has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing +the Work or Derivative Works thereof, You may choose to offer, +and charge a fee for, acceptance of support, warranty, indemnity, +or other liability obligations and/or rights consistent with this +License. However, in accepting such obligations, You may act only +on Your own behalf and on Your sole responsibility, not on behalf +of any other Contributor, and only if You agree to indemnify, +defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason +of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Flora License to your work + +To apply the Flora License to your work, attach the following +boilerplate notice, with the fields enclosed by brackets "[]" +replaced with your own identifying information. (Don't include +the brackets!) The text should be enclosed in the appropriate +comment syntax for the file format. We also recommend that a +file or class name and description of purpose be included on the +same "printed page" as the copyright notice for easier +identification within third-party archives. + + Copyright 2015 Samsung Electronics Co., Ltd. + + Licensed under the Flora License, Version 1.1 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://floralicense.org/license/ + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/lib/tau/VERSION b/lib/tau/VERSION new file mode 100644 index 0000000..548e08a --- /dev/null +++ b/lib/tau/VERSION @@ -0,0 +1 @@ +0.13.46 diff --git a/lib/tau/wearable/js/tau.js b/lib/tau/wearable/js/tau.js new file mode 100644 index 0000000..7aeb4c0 --- /dev/null +++ b/lib/tau/wearable/js/tau.js @@ -0,0 +1,46157 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +(function(window, document, undefined) { +'use strict'; +var ns = window.tau = window.tau || {}, +nsConfig = window.tauConfig = window.tauConfig || {}; +nsConfig.rootNamespace = 'tau'; +nsConfig.fileName = 'tau'; +ns.version = '0.13.46'; +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* global window, define */ +/* eslint-disable no-console */ +/** + * #Core namespace + * Object contains main framework methods. + * @class ns + * @author Maciej Urbanski + * @author Krzysztof Antoszek + * @author Maciej Moczulski + * @author Piotr Karny + * @author Tomasz Lukawski + */ +(function (document, console) { + "use strict"; + var idNumberCounter = 0, + currentDate = +new Date(), + slice = [].slice, + rootNamespace = "", + fileName = "", + infoForLog = function (args) { + var dateNow = new Date(); + + args.unshift("[" + rootNamespace + "][" + dateNow.toLocaleString() + "]"); + }, + ns = window.ns || window.tau || {}, + nsConfig = window.nsConfig || window.tauConfig || {}; + + ns.info = ns.info || { + profile: "custom" + }; + ns.tauPerf = ns.tauPerf || {}; + + window.ns = ns; + window.nsConfig = nsConfig; + + window.tau = ns; + window.tauConfig = nsConfig; + + rootNamespace = nsConfig.rootNamespace; + fileName = nsConfig.fileName; + + /** + * Return unique id + * @method getUniqueId + * @static + * @return {string} + * @member ns + */ + ns.getUniqueId = function () { + return rootNamespace + "-" + ns.getNumberUniqueId() + "-" + currentDate; + }; + + /** + * Return unique id + * @method getNumberUniqueId + * @static + * @return {number} + * @member ns + */ + ns.getNumberUniqueId = function () { + return idNumberCounter++; + }; + + /** + * logs supplied messages/arguments + * @method log + * @static + * @member ns + */ + ns.log = function () { + var args = slice.call(arguments); + + infoForLog(args); + if (console) { + console.log.apply(console, args); + } + }; + + /** + * logs supplied messages/arguments ad marks it as warning + * @method warn + * @static + * @member ns + */ + ns.warn = function () { + var args = slice.call(arguments); + + infoForLog(args); + if (console) { + console.warn.apply(console, args); + } + }; + + /** + * logs supplied messages/arguments and marks it as error + * @method error + * @static + * @member ns + */ + ns.error = function () { + var args = slice.call(arguments); + + infoForLog(args); + if (console) { + console.error.apply(console, args); + } + }; + + /** + * get from nsConfig + * @method getConfig + * @param {string} key + * @param {*} [defaultValue] value returned when config is not set + * @return {*} + * @static + * @member ns + */ + ns.getConfig = function (key, defaultValue) { + return nsConfig[key] === undefined ? defaultValue : nsConfig[key]; + }; + + /** + * set in nsConfig + * @method setConfig + * @param {string} key + * @param {*} value + * @param {boolean} [asDefault=false] value should be treated as default (doesn't overwrites + * the config[key] if it already exists) + * @static + * @member ns + */ + ns.setConfig = function (key, value, asDefault) { + if (!asDefault || nsConfig[key] === undefined) { + nsConfig[key] = value; + } + }; + + /** + * Return path for framework script file. + * @method getFrameworkPath + * @return {?string} + * @member ns + */ + ns.getFrameworkPath = function () { + var scripts = document.getElementsByTagName("script"), + countScripts = scripts.length, + i, + url, + arrayUrl, + count; + + for (i = 0; i < countScripts; i++) { + url = scripts[i].src; + arrayUrl = url.split("/"); + count = arrayUrl.length; + if (arrayUrl[count - 1] === fileName + ".js" || + arrayUrl[count - 1] === fileName + ".min.js") { + return arrayUrl.slice(0, count - 1).join("/"); + } + } + return null; + }; + + }(window.document, window.console)); + +/*global window, ns, define*/ +/*jslint bitwise: true */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @class ns.support + * @author Maciej Urbanski + */ +(function (window, document) { + "use strict"; + var isTizen = !(typeof window.tizen === "undefined"); + + function isCircleShape() { + var testDivElement = document.createElement("div"), + fakeBodyElement = document.createElement("body"), + htmlElement = document.getElementsByTagName("html")[0], + style = getComputedStyle(testDivElement), + isCircle; + + testDivElement.classList.add("is-circle-test"); + fakeBodyElement.appendChild(testDivElement); + htmlElement.insertBefore(fakeBodyElement, htmlElement.firstChild); + isCircle = style.width === "1px"; + htmlElement.removeChild(fakeBodyElement); + + // to support circle in browser by additional parameter in url + /* istanbul ignore if */ + if (window.location.search === "?circle") { + /* istanbul ignore next: we can't test this part because set location is not supported in + test environment */ + isCircle = true; + } + + return isCircle; + } + + ns.support = { + cssTransitions: true, + mediaquery: true, + cssPseudoElement: true, + touchOverflow: true, + cssTransform3d: true, + boxShadow: true, + scrollTop: 0, + dynamicBaseTag: true, + cssPointerEvents: false, + boundingRect: true, + browser: { + ie: false, + tizen: isTizen + }, + shape: { + circle: isTizen ? window.matchMedia("(-tizen-geometric-shape: circle)").matches : + isCircleShape() + }, + gradeA: function () { + return true; + }, + isCircleShape: isCircleShape + }; + }(window, window.document)); + +/*global window, ns, define*/ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/*jslint bitwise: true */ +/* + * @author Piotr Karny + */ +(function () { + "use strict"; + // Default configuration properties for wearable + ns.setConfig("autoBuildOnPageChange", false, true); + + if (ns.support.shape.circle) { + ns.setConfig("pageTransition", "pop", true); + ns.setConfig("popupTransition", "pop", true); + + ns.setConfig("popupFullSize", true, true); + ns.setConfig("scrollEndEffectArea", "screen", true); + ns.setConfig("enablePageScroll", true, true); + ns.setConfig("enablePopupScroll", true, true); + } else { + ns.setConfig("popupTransition", "slideup", true); + ns.setConfig("enablePageScroll", false, true); + ns.setConfig("enablePopupScroll", false, true); + } + // .. other possible options + // ns.setConfig('autoInitializePage', true); + // ns.setConfig('pageContainer', document.body); // defining application container for wearable + + }()); + +/*global window, define, ns*/ +/*jslint bitwise: true */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * @author Maciej Urbanski + * @author Piotr Karny + */ +(function () { + "use strict"; + + // Default configuration properties + ns.setConfig("rootDir", ns.getFrameworkPath(), true); + ns.setConfig("version", "", true); + ns.setConfig("allowCrossDomainPages", false, true); + ns.setConfig("domCache", false, true); + // .. other possible options + ns.setConfig("autoBuildOnPageChange", true, true); + ns.setConfig("autoInitializePage", true, true); + ns.setConfig("dynamicBaseEnabled", true, true); + ns.setConfig("pageTransition", "none", true); + ns.setConfig("popupTransition", "none", true); + ns.setConfig("popupFullSize", false, true); + ns.setConfig("scrollEndEffectArea", "content", true); + ns.setConfig("enablePopupScroll", false, true); + // ns.setConfig('container', document.body); // for defining application container + // same as above, but for wearable version + ns.setConfig("pageContainer", document.body, true); + ns.setConfig("findProfileFile", false, true); + + }()); + +/*global window, ns, define, XMLHttpRequest, console, Blob */ +/*jslint nomen: true, browser: true, plusplus: true */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * #Utilities + * + * The Tizen Advanced UI (TAU) framework provides utilities for easy-developing + * and fully replaceable with jQuery method. When user using these DOM and + * selector methods, it provide more light logic and it proves performance + * of web app. The following table displays the utilities provided by the + * TAU framework. + * + * @class ns.util + * @author Maciej Urbanski + * @author Krzysztof Antoszek + */ +(function (window, document, ns) { + "use strict"; + var currentFrame = null, + util = ns.util || {}, + // frames callbacks which should be run in next request animation frame + waitingFrames = [], + slice = [].slice, + // inform that loop was added to request animation frame callback + loopWork = false; + + /** + * Function which is use as workaround when any type of request animation frame not exists + * @param {Function} callback + * @method _requestAnimationFrameOnSetTimeout + * @static + * @member ns.util + * @protected + */ + util._requestAnimationFrameOnSetTimeout = function (callback) { + currentFrame = window.setTimeout(callback.bind(callback, +new Date()), 1000 / 60); + }; + + /** + * Function which support every request animation frame. + * @method _loop + * @protected + * @static + * @member ns.util + */ + util._loop = function () { + var loopWaitingFrames = slice.call(waitingFrames), + currentFrameFunction = loopWaitingFrames.shift(), + loopTime = performance.now(); + + waitingFrames = []; + + while (currentFrameFunction) { + currentFrameFunction(); + if (performance.now() - loopTime < 15) { + currentFrameFunction = loopWaitingFrames.shift(); + } else { + currentFrameFunction = null; + } + } + if (loopWaitingFrames.length || waitingFrames.length) { + waitingFrames.unshift.apply(waitingFrames, loopWaitingFrames); + util.windowRequestAnimationFrame(util._loop); + } else { + loopWork = false; + } + }; + + /** + * Find browser prefixed request animation frame function. + * @method _getRequestAnimationFrame + * @protected + * @static + * @member ns.util + */ + util._getRequestAnimationFrame = function () { + return (window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + util._requestAnimationFrameOnSetTimeout).bind(window); + }; + + /** + * Original requestAnimationFrame from object window. + * @method windowRequestAnimationFrame + * @static + * @member ns.util + */ + util.windowRequestAnimationFrame = util._getRequestAnimationFrame(); + + /** + * Special requestAnimationFrame function which add functions to queue of callbacks + * @method requestAnimationFrame + * @static + * @member ns.util + */ + util.requestAnimationFrame = function (callback) { + waitingFrames.push(callback); + if (!loopWork) { + util.windowRequestAnimationFrame(util._loop); + loopWork = true; + } + }; + + util._cancelAnimationFrameOnSetTimeout = function () { + // probably wont work if there is any more than 1 + // active animationFrame but we are trying anyway + window.clearTimeout(currentFrame); + }; + + util._getCancelAnimationFrame = function () { + return (window.cancelAnimationFrame || + window.webkitCancelAnimationFrame || + window.mozCancelAnimationFrame || + window.oCancelAnimationFrame || + window.msCancelAnimationFrame || + util._cancelAnimationFrameOnSetTimeout).bind(window); + }; + + util.cancelAnimationFrame = util._getCancelAnimationFrame(); + + /** + * fetchSync retrieves a text document synchronously, returns null on error + * @param {string} url + * @param {=string} [mime=""] Mime type of the resource + * @return {string|null} + * @static + * @member ns.util + */ + function fetchSync(url, mime) { + var xhr = new XMLHttpRequest(), + status; + + xhr.open("get", url, false); + if (mime) { + xhr.overrideMimeType(mime); + } + xhr.send(); + if (xhr.readyState === 4) { + status = xhr.status; + if (status === 200 || (status === 0 && xhr.responseText)) { + return xhr.responseText; + } + } + + return null; + } + + util.fetchSync = fetchSync; + + /** + * Removes all script tags with src attribute from document and returns them + * @param {HTMLElement} container + * @return {Array.} + * @protected + * @static + * @member ns.util + */ + function removeExternalScripts(container) { + var scripts = slice.call(container.querySelectorAll("script[src]")), + i = scripts.length, + script; + + while (--i >= 0) { + script = scripts[i]; + script.parentNode.removeChild(script); + } + + return scripts; + } + + util._removeExternalScripts = removeExternalScripts; + + /** + * Evaluates code, reason for a function is for an atomic call to evaluate code + * since most browsers fail to optimize functions with try-catch blocks, so this + * minimizes the effect, returns the function to run + * @param {string} code + * @return {Function} + * @static + * @member ns.util + */ + function safeEvalWrap(code) { + return function () { + try { + window.eval(code); + } catch (e) { + if (e.stack) { + ns.error(e.stack); + } else if (e.name && e.message) { + ns.error(e.name, e.message); + } else { + ns.error(e); + } + } + }; + } + + util.safeEvalWrap = safeEvalWrap; + + /** + * Calls functions in supplied queue (array) + * @param {Array.} functionQueue + * @static + * @member ns.util + */ + function batchCall(functionQueue) { + var i, + length = functionQueue.length; + + for (i = 0; i < length; ++i) { + functionQueue[i](); + } + } + + util.batchCall = batchCall; + + /** + * Creates new script elements for scripts gathered from a different document + * instance, blocks asynchronous evaluation (by renaming src attribute) and + * returns an array of functions to run to evaluate those scripts + * @param {Array.} scripts + * @param {HTMLElement} container + * @return {Array.} + * @protected + * @static + * @member ns.util + */ + function createScriptsSync(scripts, container) { + var scriptElement, + scriptBody, + i, + length, + queue = []; + + // proper order of execution + for (i = 0, length = scripts.length; i < length; ++i) { + scriptBody = util.fetchSync(scripts[i].src, "text/plain"); + if (scriptBody) { + scriptElement = document.adoptNode(scripts[i]); + scriptElement.setAttribute("data-src", scripts[i].src); + scriptElement.removeAttribute("src"); // block evaluation + queue.push(util.safeEvalWrap(scriptBody)); + if (container) { + container.appendChild(scriptElement); + } + } + } + + return queue; + } + + util._createScriptsSync = createScriptsSync; + + /** + * Method make asynchronous call of function + * @method async + * @inheritdoc #requestAnimationFrame + * @member ns.util + * @static + */ + util.async = util.requestAnimationFrame; + + /** + * Appends element from different document instance to current document in the + * container element and evaluates scripts (synchronously) + * @param {HTMLElement} element + * @param {HTMLElement} container + * @return {HTMLElement} + * @method importEvaluateAndAppendElement + * @member ns.util + * @static + */ + util.importEvaluateAndAppendElement = function (element, container) { + var externalScriptsQueue = + util._createScriptsSync(util._removeExternalScripts(element), element), + newNode = document.importNode(element, true); + + container.appendChild(newNode); // append and eval inline + util.batchCall(externalScriptsQueue); + + return newNode; + }; + + /** + * Checks if specified string is a number or not + * @method isNumber + * @param {string} query + * @return {boolean} + * @member ns.util + * @static + */ + util.isNumber = function (query) { + var parsed = parseFloat(query); + + return !isNaN(parsed) && isFinite(parsed); + }; + + /** + * Reappear script tags to DOM structure to correct run script + * @method runScript + * @param {string} baseUrl + * @param {HTMLScriptElement} script + * @member ns.util + * @deprecated 2.3 + */ + util.runScript = function (baseUrl, script) { + var newScript = document.createElement("script"), + scriptData, + i, + scriptAttributes = slice.call(script.attributes), + src = script.getAttribute("src"), + attribute, + status; + + // 'src' may become null when none src attribute is set + if (src !== null) { + src = util.path.makeUrlAbsolute(src, baseUrl); + } + + //Copy script tag attributes + i = scriptAttributes.length; + while (--i >= 0) { + attribute = scriptAttributes[i]; + if (attribute.name !== "src") { + newScript.setAttribute(attribute.name, attribute.value); + } else { + newScript.setAttribute("data-src", attribute.value); + } + } + + if (src) { + scriptData = util.fetchSync(src, "text/plain"); + } else { + scriptData = script.textContent; + } + + if (scriptData) { + // add the returned content to a newly created script tag + newScript.src = window.URL.createObjectURL(new Blob([scriptData], {type: "text/javascript"})); + newScript.textContent = scriptData; // for compatibility with some libs ex. template systems + } + script.parentNode.replaceChild(newScript, script); + }; + + ns.util = util; + }(window, window.document, ns)); + +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/*global window, ns, define */ +/** + * #Array Utility + * + * Utility helps work with arrays. + * + * @class ns.util.array + */ +(function (ns) { + "use strict"; + + /** + * Convert values to common type and return information about type string or not. + * @param {number|string} low + * @param {number|string} high + * @return {{inival: *, endval: *, chars: boolean}} + */ + function convertTypes(low, high) { + var inival, + endval, + chars = false; + + if (isNaN(low) && isNaN(high)) { + chars = true; + inival = low.charCodeAt(0); + endval = high.charCodeAt(0); + } else { + inival = (isNaN(low) ? 0 : low); + endval = (isNaN(high) ? 0 : high); + } + return { + inival: inival, + endval: endval, + chars: chars + }; + } + + /** + * Create an array containing the range of integers or characters + * from low to high (inclusive) + * @method range + * @param {number|string} low + * @param {number|string} high + * @param {number} step + * @static + * @return {Array} array containing continuos elements + * @member ns.util.array + */ + function range(low, high, step) { + // Create an array containing the range of integers or characters + // from low to high (inclusive) + // + // version: 1107.2516 + // discuss at: http://phpjs.org/functions/range + // + original by: Waldo Malqui Silva + // * example 1: range ( 0, 12 ); + // * returns 1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + // * example 2: range( 0, 100, 10 ); + // * returns 2: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + // * example 3: range( 'a', 'i' ); + // * returns 3: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'] + // * example 4: range( 'c', 'a' ); + // * returns 4: ['c', 'b', 'a'] + var matrix = [], + inival, + endval, + plus, + walker = step || 1, + chars, + typeData; + + typeData = convertTypes(low, high); + inival = typeData.inival; + endval = typeData.endval; + chars = typeData.chars; + + plus = inival <= endval; + if (plus) { + while (inival <= endval) { + matrix.push((chars ? String.fromCharCode(inival) : inival)); + inival += walker; + } + } else { + while (inival >= endval) { + matrix.push((chars ? String.fromCharCode(inival) : inival)); + inival -= walker; + } + } + + return matrix; + } + + function isCorrectType(object) { + return Array.isArray(object) || object instanceof NodeList || typeof object === "function"; + } + + function hasCorrectLength(object) { + var length = object.length; + + return (length === 0 || typeof length === "number" && length > 0 && (length - 1) in object); + } + + /** + * Check object is array-like (array-like include array and + * collection) + * @method isArrayLike + * @param {Object} object + * @return {boolean} Whether array-like object or not + * @member ns.util.array + * @static + */ + function isArrayLike(object) { + + // if object exists and is different from window + // window object has length property + if (object && object !== object.window) { + // If length value is not number, object is not array and collection. + // Collection type is not array but has length value. + // e.g) Array.isArray(document.childNodes) ==> false + return isCorrectType(object) && hasCorrectLength(object); + } + return false; + } + + /** + * Faster version of standard forEach method in array + * Confirmed that this method is 20 times faster then native + * @method forEach + * @param {Array} array + * @param {Function} callback + * @member ns.util.array + * @static + */ + function forEach(array, callback) { + var i, + length, + convertedArray = array; + + if (!(array instanceof Array)) { + convertedArray = [].slice.call(array); + } + length = convertedArray.length; + for (i = 0; i < length; i++) { + callback(convertedArray[i], i, convertedArray); + } + } + + + /** + * Faster version of standard filter method in array + * @method filter + * @param {Array} array + * @param {Function} callback + * @member ns.util.array + * @static + */ + function filter(array, callback) { + var result = [], + i, + length, + value, + convertedArray = array; + + if (!(array instanceof Array)) { + convertedArray = [].slice.call(array); + } + length = convertedArray.length; + for (i = 0; i < length; i++) { + value = convertedArray[i]; + if (callback(value, i, convertedArray)) { + result.push(value); + } + } + return result; + } + + /** + * Faster version of standard map method in array + * Confirmed that this method is 60% faster then native + * @method map + * @param {Array} array + * @param {Function} callback + * @member ns.util.array + * @static + */ + function map(array, callback) { + var result = [], + i, + length, + convertedArray = array; + + if (!(array instanceof Array)) { + convertedArray = [].slice.call(array); + } + length = convertedArray.length; + for (i = 0; i < length; i++) { + result.push(callback(convertedArray[i], i, convertedArray)); + } + return result; + } + + /** + * Faster version of standard reduce method in array + * Confirmed that this method is 60% faster then native + * @method reduce + * @param {Array} array + * @param {Function} callback + * @param {*} [initialValue] + * @member ns.util.array + * @return {*} + * @static + */ + function reduce(array, callback, initialValue) { + var i, + length, + value, + result = initialValue, + convertedArray = array; + + if (!(array instanceof Array)) { + convertedArray = [].slice.call(array); + } + length = convertedArray.length; + for (i = 0; i < length; i++) { + value = convertedArray[i]; + if (result === undefined && i === 0) { + result = value; + } else { + result = callback(result, value, i, convertedArray); + } + } + return result; + } + + ns.util.array = { + range: range, + isArrayLike: isArrayLike, + forEach: forEach, + filter: filter, + map: map, + reduce: reduce + }; + + }(ns)); + +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +/* global ns, define, CustomEvent */ +/** + * #Events + * + * The Tizen Advanced UI (TAU) framework provides events optimized for the Tizen + * Web application. The following table displays the events provided by the TAU + * framework. + * @class ns.event + */ +(function (window, ns) { + "use strict"; + /** + * Checks if specified variable is a array or not + * @method isArray + * @return {boolean} + * @member ns.event + * @private + * @static + */ + var instances = [], + isArray = Array.isArray, + isArrayLike = ns.util.array.isArrayLike, + /** + * @property {RegExp} SPLIT_BY_SPACES_REGEXP + */ + SPLIT_BY_SPACES_REGEXP = /\s+/g, + + /** + * Returns trimmed value + * @method trim + * @param {string} value + * @return {string} trimmed string + * @static + * @private + * @member ns.event + */ + trim = function (value) { + return value.trim(); + }, + + /** + * Split string to array + * @method getEventsListeners + * @param {string|Array|Object} names string with one name of event, many names of events + * divided by spaces, array with names of widgets or object in which keys are names of + * events and values are callbacks + * @param {Function} globalListener + * @return {Array} + * @static + * @private + * @member ns.event + */ + getEventsListeners = function (names, globalListener) { + var name, + result = [], + i; + + if (typeof names === "string") { + names = names.split(SPLIT_BY_SPACES_REGEXP).map(trim); + } + + if (isArray(names)) { + for (i = 0; i < names.length; i++) { + result.push({type: names[i], callback: globalListener}); + } + } else { + for (name in names) { + if (names.hasOwnProperty(name)) { + result.push({type: name, callback: names[name]}); + } + } + } + return result; + }; + + /** + * Find instance by element + * @method findInstance + * @param {HTMLElement} element + * @return {ns.event.gesture.Instance} + * @member ns.event + * @static + * @private + */ + function findInstance(element) { + var instance; + + instances.forEach(function (item) { + if (item.element === element) { + instance = item.instance; + } + }); + return instance; + } + + /** + * Remove instance from instances by element + * @method removeInstance + * @param {HTMLElement} element + * @member ns.event + * @static + * @private + */ + function removeInstance(element) { + instances.forEach(function (item, key) { + if (item.element === element) { + instances.splice(key, 1); + } + }); + } + + + ns.event = { + + /** + * Triggers custom event fastOn element + * The return value is false, if at least one of the event + * handlers which handled this event, called preventDefault. + * Otherwise it returns true. + * @method trigger + * @param {HTMLElement|HTMLDocument} element + * @param {string} type + * @param {?*} [data=null] + * @param {boolean=} [bubbles=true] + * @param {boolean=} [cancelable=true] + * @return {boolean} + * @member ns.event + * @static + */ + trigger: function (element, type, data, bubbles, cancelable) { + var evt = new CustomEvent(type, { + "detail": data, + //allow event to bubble up, required if we want to allow to listen fastOn document etc + bubbles: typeof bubbles === "boolean" ? bubbles : true, + cancelable: typeof cancelable === "boolean" ? cancelable : true + }); + return element.dispatchEvent(evt); + }, + + /** + * Prevent default on original event + * @method preventDefault + * @param {Event} event + * @member ns.event + * @static + */ + preventDefault: function (event) { + var originalEvent = event._originalEvent; + // @todo this.isPropagationStopped = returnTrue; + + if (originalEvent && originalEvent.preventDefault) { + originalEvent.preventDefault(); + } + event.preventDefault(); + }, + + /** + * Stop event propagation + * @method stopPropagation + * @param {Event} event + * @member ns.event + * @static + */ + stopPropagation: function (event) { + var originalEvent = event._originalEvent; + // @todo this.isPropagationStopped = returnTrue; + + if (originalEvent && originalEvent.stopPropagation) { + originalEvent.stopPropagation(); + } + event.stopPropagation(); + }, + + /** + * Stop event propagation immediately + * @method stopImmediatePropagation + * @param {Event} event + * @member ns.event + * @static + */ + stopImmediatePropagation: function (event) { + var originalEvent = event._originalEvent; + // @todo this.isPropagationStopped = returnTrue; + + if (originalEvent && originalEvent.stopImmediatePropagation) { + originalEvent.stopImmediatePropagation(); + } + event.stopImmediatePropagation(); + }, + + /** + * Return document relative cords for event + * @method documentRelativeCoordsFromEvent + * @param {Event} event + * @return {Object} + * @return {number} return.x + * @return {number} return.y + * @member ns.event + * @static + */ + documentRelativeCoordsFromEvent: function (event) { + var _event = event ? event : window.event, + client = { + x: _event.clientX, + y: _event.clientY + }, + page = { + x: _event.pageX, + y: _event.pageY + }, + posX = 0, + posY = 0, + touch0, + body = document.body, + documentElement = document.documentElement; + + if (event.type.match(/^touch/)) { + touch0 = _event.targetTouches[0] || _event.originalEvent.targetTouches[0]; + page = { + x: touch0.pageX, + y: touch0.pageY + }; + client = { + x: touch0.clientX, + y: touch0.clientY + }; + } + + if (page.x || page.y) { + posX = page.x; + posY = page.y; + } else if (client.x || client.y) { + posX = client.x + body.scrollLeft + documentElement.scrollLeft; + posY = client.y + body.scrollTop + documentElement.scrollTop; + } + + return {x: posX, y: posY}; + }, + + /** + * Return target relative cords for event + * @method targetRelativeCoordsFromEvent + * @param {Event} event + * @return {Object} + * @return {number} return.x + * @return {number} return.y + * @member ns.event + * @static + */ + targetRelativeCoordsFromEvent: function (event) { + var target = event.target, + cords = { + x: event.offsetX, + y: event.offsetY + }; + + if (cords.x === undefined || isNaN(cords.x) || + cords.y === undefined || isNaN(cords.y)) { + cords = ns.event.documentRelativeCoordsFromEvent(event); + cords.x -= target.offsetLeft; + cords.y -= target.offsetTop; + } + + return cords; + }, + + /** + * Add event listener to element + * @method fastOn + * @param {HTMLElement} element + * @param {string} type + * @param {Function} listener + * @param {boolean} [useCapture=false] + * @member ns.event + * @static + */ + fastOn: function (element, type, listener, useCapture) { + element.addEventListener(type, listener, useCapture || false); + }, + + /** + * Remove event listener to element + * @method fastOff + * @param {HTMLElement} element + * @param {string} type + * @param {Function} listener + * @param {boolean} [useCapture=false] + * @member ns.event + * @static + */ + fastOff: function (element, type, listener, useCapture) { + element.removeEventListener(type, listener, useCapture || false); + }, + + /** + * Add event listener to element with prefixes for all browsers + * + * @example + * tau.event.prefixedFastOn(document, "animationEnd", function() { + * console.log("animation ended"); + * }); + * // write "animation ended" on console on event "animationEnd", "webkitAnimationEnd", "mozAnimationEnd", "msAnimationEnd", "oAnimationEnd" + * + * @method fastPrefixedOn + * @param {HTMLElement} element + * @param {string} type + * @param {Function} listener + * @param {boolean} [useCapture=false] + * @member ns.event + * @static + */ + prefixedFastOn: function (element, type, listener, useCapture) { + var nameForPrefix = type.charAt(0).toLocaleUpperCase() + type.substring(1); + + element.addEventListener(type.toLowerCase(), listener, useCapture || false); + element.addEventListener("webkit" + nameForPrefix, listener, useCapture || false); + element.addEventListener("moz" + nameForPrefix, listener, useCapture || false); + element.addEventListener("ms" + nameForPrefix, listener, useCapture || false); + element.addEventListener("o" + nameForPrefix.toLowerCase(), listener, useCapture || false); + }, + + /** + * Remove event listener to element with prefixes for all browsers + * + * @example + * tau.event.prefixedFastOff(document, "animationEnd", functionName); + * // remove listeners functionName on events "animationEnd", "webkitAnimationEnd", "mozAnimationEnd", "msAnimationEnd", "oAnimationEnd" + * + * @method fastPrefixedOff + * @param {HTMLElement} element + * @param {string} type + * @param {Function} listener + * @param {boolean} [useCapture=false] + * @member ns.event + * @static + */ + prefixedFastOff: function (element, type, listener, useCapture) { + var nameForPrefix = type.charAt(0).toLocaleUpperCase() + type.substring(1); + + element.removeEventListener(type.toLowerCase(), listener, useCapture || false); + element.removeEventListener("webkit" + nameForPrefix, listener, useCapture || false); + element.removeEventListener("moz" + nameForPrefix, listener, useCapture || false); + element.removeEventListener("ms" + nameForPrefix, listener, useCapture || false); + element.removeEventListener("o" + nameForPrefix.toLowerCase(), listener, useCapture || false); + }, + + /** + * Add event listener to element that can be added addEventListener + * @method on + * @param {HTMLElement|HTMLDocument|Window} element + * @param {string|Array|Object} type + * @param {Function} listener + * @param {boolean} [useCapture=false] + * @member ns.event + * @static + */ + on: function (element, type, listener, useCapture) { + var i, + j, + elementsLength, + typesLength, + elements, + listeners; + + if (isArrayLike(element)) { + elements = element; + } else { + elements = [element]; + } + elementsLength = elements.length; + listeners = getEventsListeners(type, listener); + typesLength = listeners.length; + for (i = 0; i < elementsLength; i++) { + if (typeof elements[i].addEventListener === "function") { + for (j = 0; j < typesLength; j++) { + ns.event.fastOn(elements[i], listeners[j].type, listeners[j].callback, useCapture); + } + } + } + }, + + /** + * Remove event listener to element + * @method off + * @param {HTMLElement|HTMLDocument|Window} element + * @param {string|Array|Object} type + * @param {Function} listener + * @param {boolean} [useCapture=false] + * @member ns.event + * @static + */ + off: function (element, type, listener, useCapture) { + var i, + j, + elementsLength, + typesLength, + elements, + listeners; + + if (isArrayLike(element)) { + elements = element; + } else { + elements = [element]; + } + elementsLength = elements.length; + listeners = getEventsListeners(type, listener); + typesLength = listeners.length; + for (i = 0; i < elementsLength; i++) { + if (typeof elements[i].addEventListener === "function") { + for (j = 0; j < typesLength; j++) { + ns.event.fastOff(elements[i], listeners[j].type, listeners[j].callback, useCapture); + } + } + } + }, + + /** + * Add event listener to element only for one trigger + * @method one + * @param {HTMLElement|HTMLDocument|window} element + * @param {string|Array|Object} type + * @param {Function} listener + * @param {boolean} [useCapture=false] + * @member ns.event + * @static + */ + one: function (element, type, listener, useCapture) { + var arraySlice = [].slice, + i, + j, + elementsLength, + typesLength, + elements, + listeners, + callbacks = []; + + if (isArrayLike(element)) { + elements = arraySlice.call(element); + } else { + elements = [element]; + } + elementsLength = elements.length; + // pair type with listener + listeners = getEventsListeners(type, listener); + typesLength = listeners.length; + // on each element + for (i = 0; i < elementsLength; i++) { + // if element has possibility of add listener + if (typeof elements[i].addEventListener === "function") { + callbacks[i] = []; + // for each event type + for (j = 0; j < typesLength; j++) { + callbacks[i][j] = (function (i, j) { + var args = arraySlice.call(arguments); + + ns.event.fastOff(elements[i], listeners[j].type, callbacks[i][j], useCapture); + // remove the first argument of binding function + args.shift(); + // remove the second argument of binding function + args.shift(); + listeners[j].callback.apply(this, args); + }).bind(null, i, j); + ns.event.fastOn(elements[i], listeners[j].type, callbacks[i][j], useCapture); + } + } + } + }, + + // disable is required because method has changing arguments + /* eslint-disable jsdoc/check-param-names */ + + /** + * Enable gesture handling on given HTML element or object + * @method enableGesture + * @param {HTMLElement} element + * @param {...Object} [gesture] Gesture object {@link ns.event.gesture} + * @member ns.event + */ + enableGesture: function (element) { + var gestureInstance = findInstance(element), + length = arguments.length, + i = 1; + + if (!gestureInstance) { + gestureInstance = new ns.event.gesture.Instance(element); + instances.push({element: element, instance: gestureInstance}); + } + + for (; i < length; i++) { + gestureInstance.addDetector(arguments[i]); + } + }, + + /** + * Disable gesture handling from given HTML element or object + * @method disableGesture + * @param {HTMLElement} element + * @param {...Object} [gesture] Gesture object {@link ns.event.gesture} + * @member ns.event + */ + disableGesture: function (element) { + var gestureInstance = findInstance(element), + length = arguments.length, + i = 1; + + if (!gestureInstance) { + return; + } + + if (length > 1) { + gestureInstance.removeDetector(arguments[i]); + } else { + gestureInstance.destroy(); + removeInstance(element); + } + } + + /* eslint-disable jsdoc/check-param-names */ + }; + + }(window, ns)); + +/*global window, ns, define */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * #Info + * + * Various TAU information + * @class ns.info + */ +(function (window, document) { + "use strict"; + /** + * @property {Object} info + * @property {string} [info.profile="default"] Current runtime profile + * @property {string} [info.theme="default"] Current runtime theme + * @property {string} info.version Current runtime version + * @member ns.info + * @static + */ + var eventUtils = ns.event, + info = { + profile: "default", + theme: "default", + version: ns.version, + + /** + * Refreshes information about runtime + * @method refreshTheme + * @param {Function} done Callback run when the theme is discovered + * @member ns.info + * @return {null|String} + * @static + */ + refreshTheme: function (done) { + var el = document.createElement("span"), + parent = document.body, + themeName; + + if (document.readyState !== "interactive" && document.readyState !== "complete") { + eventUtils.fastOn(document, "DOMContentLoaded", this.refreshTheme.bind(this, done)); + return null; + } + el.classList.add("tau-info-theme"); + + parent.appendChild(el); + themeName = window.getComputedStyle(el, ":after").content; + parent.removeChild(el); + + if (themeName && themeName.length > 0) { + this.theme = themeName; + } + + themeName = themeName || null; + + if (done) { + done(themeName); + } + + return themeName; + } + }; + + info.refreshTheme(); + + ns.info = info; + }(window, window.document)); + +/*global define: false, window: false, ns: false */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * #Selectors Utility + * Object contains functions to get HTML elements by different selectors. + * @class ns.util.selectors + * @author Maciej Urbanski + * @author Krzysztof Antoszek + * @author Jadwiga Sosnowska + * @author Damian Osipiuk + */ +(function (document, ns) { + "use strict"; + /** + * @method slice Alias for array slice method + * @member ns.util.selectors + * @private + * @static + */ + var slice = [].slice, + /** + * @method matchesSelectorType + * @return {string|boolean} + * @member ns.util.selectors + * @private + * @static + */ + matchesSelectorType = (function () { + var el = document.createElement("div"); + + if (typeof el.webkitMatchesSelector === "function") { + return "webkitMatchesSelector"; + } + + if (typeof el.mozMatchesSelector === "function") { + return "mozMatchesSelector"; + } + + if (typeof el.msMatchesSelector === "function") { + return "msMatchesSelector"; + } + + if (typeof el.matchesSelector === "function") { + return "matchesSelector"; + } + + if (typeof el.matches === "function") { + return "matches"; + } + + return ""; + }()); + + /** + * Prefix selector with 'data-' and namespace if present + * @method getDataSelector + * @param {string} selector + * @return {string} + * @member ns.util.selectors + * @private + * @static + */ + function getDataSelector(selector) { + var namespace = ns.getConfig("namespace"); + + return "[data-" + (namespace ? namespace + "-" : "") + selector + "]"; + } + + /** + * Runs matches implementation of matchesSelector + * method on specified element + * @method matchesSelector + * @param {HTMLElement} element + * @param {string} selector + * @return {boolean} + * @static + * @member ns.util.selectors + */ + function matchesSelector(element, selector) { + if (matchesSelectorType && element[matchesSelectorType]) { + return element[matchesSelectorType](selector); + } + return false; + } + + /** + * Return array with all parents of element. + * @method parents + * @param {HTMLElement} element + * @return {Array} + * @member ns.util.selectors + * @private + * @static + */ + function parents(element) { + var items = [], + current = element.parentNode; + + while (current && current !== document) { + items.push(current); + current = current.parentNode; + } + return items; + } + + /** + * Checks if given element and its ancestors matches given function + * @method closest + * @param {HTMLElement} element + * @param {Function} testFunction + * @return {?HTMLElement} + * @member ns.util.selectors + * @static + * @private + */ + function closest(element, testFunction) { + var current = element; + + while (current && current !== document) { + if (testFunction(current)) { + return current; + } + current = current.parentNode; + } + return null; + } + + /** + * @method testSelector + * @param {string} selector + * @param {HTMLElement} node + * @return {boolean} + * @member ns.util.selectors + * @static + * @private + */ + function testSelector(selector, node) { + return matchesSelector(node, selector); + } + + /** + * @method testClass + * @param {string} className + * @param {HTMLElement} node + * @return {boolean} + * @member ns.util.selectors + * @static + * @private + */ + function testClass(className, node) { + return node && node.classList && node.classList.contains(className); + } + + /** + * @method testTag + * @param {string} tagName + * @param {HTMLElement} node + * @return {boolean} + * @member ns.util.selectors + * @static + * @private + */ + function testTag(tagName, node) { + return node.tagName.toLowerCase() === tagName; + } + + /** + * @class ns.util.selectors + */ + ns.util.selectors = { + matchesSelector: matchesSelector, + + /** + * Return array with children pass by given selector. + * @method getChildrenBySelector + * @param {HTMLElement} context + * @param {string} selector + * @return {Array} + * @static + * @member ns.util.selectors + */ + getChildrenBySelector: function (context, selector) { + return slice.call(context.children).filter(testSelector.bind(null, selector)); + }, + + /** + * Return array with children pass by given data-namespace-selector. + * @method getChildrenByDataNS + * @param {HTMLElement} context + * @param {string} dataSelector + * @return {Array} + * @static + * @member ns.util.selectors + */ + getChildrenByDataNS: function (context, dataSelector) { + return slice.call(context.children).filter(testSelector.bind(null, + getDataSelector(dataSelector))); + }, + + /** + * Return array with children with given class name. + * @method getChildrenByClass + * @param {HTMLElement} context + * @param {string} className + * @return {Array} + * @static + * @member ns.util.selectors + */ + getChildrenByClass: function (context, className) { + return slice.call(context.children).filter(testClass.bind(null, className)); + }, + + /** + * Return array with children with given tag name. + * @method getChildrenByTag + * @param {HTMLElement} context + * @param {string} tagName + * @return {Array} + * @static + * @member ns.util.selectors + */ + getChildrenByTag: function (context, tagName) { + return slice.call(context.children).filter(testTag.bind(null, tagName)); + }, + + /** + * Return array with all parents of element. + * @method getParents + * @param {HTMLElement} context + * @param {string} selector + * @return {Array} + * @static + * @member ns.util.selectors + */ + getParents: parents, + + /** + * Return array with all parents of element pass by given selector. + * @method getParentsBySelector + * @param {HTMLElement} context + * @param {string} selector + * @return {Array} + * @static + * @member ns.util.selectors + */ + getParentsBySelector: function (context, selector) { + return parents(context).filter(testSelector.bind(null, selector)); + }, + + /** + * Return array with all parents of element pass by given selector with namespace. + * @method getParentsBySelectorNS + * @param {HTMLElement} context + * @param {string} selector + * @return {Array} + * @static + * @member ns.util.selectors + */ + getParentsBySelectorNS: function (context, selector) { + return parents(context).filter(testSelector.bind(null, getDataSelector(selector))); + }, + + /** + * Return array with all parents of element with given class name. + * @method getParentsByClass + * @param {HTMLElement} context + * @param {string} className + * @return {Array} + * @static + * @member ns.util.selectors + */ + getParentsByClass: function (context, className) { + return parents(context).filter(testClass.bind(null, className)); + }, + + /** + * Return array with all parents of element with given tag name. + * @method getParentsByTag + * @param {HTMLElement} context + * @param {string} tagName + * @return {Array} + * @static + * @member ns.util.selectors + */ + getParentsByTag: function (context, tagName) { + return parents(context).filter(testTag.bind(null, tagName)); + }, + + /** + * Return first element from parents of element pass by selector. + * @method getClosestBySelector + * @param {HTMLElement} context + * @param {string} selector + * @return {HTMLElement} + * @static + * @member ns.util.selectors + */ + getClosestBySelector: function (context, selector) { + return closest(context, testSelector.bind(null, selector)); + }, + + /** + * Return first element from parents of element pass by selector with namespace. + * @method getClosestBySelectorNS + * @param {HTMLElement} context + * @param {string} selector + * @return {HTMLElement} + * @static + * @member ns.util.selectors + */ + getClosestBySelectorNS: function (context, selector) { + return closest(context, testSelector.bind(null, getDataSelector(selector))); + }, + + /** + * Return first element from parents of element with given class name. + * @method getClosestByClass + * @param {HTMLElement} context + * @param {string} selector + * @return {HTMLElement} + * @static + * @member ns.util.selectors + */ + getClosestByClass: function (context, selector) { + return closest(context, testClass.bind(null, selector)); + }, + + /** + * Return first element from parents of element with given tag name. + * @method getClosestByTag + * @param {HTMLElement} context + * @param {string} selector + * @return {HTMLElement} + * @static + * @member ns.util.selectors + */ + getClosestByTag: function (context, selector) { + return closest(context, testTag.bind(null, selector)); + }, + + /** + * Return array of elements from context with given data-selector + * @method getAllByDataNS + * @param {HTMLElement} context + * @param {string} dataSelector + * @return {Array} + * @static + * @member ns.util.selectors + */ + getAllByDataNS: function (context, dataSelector) { + return slice.call(context.querySelectorAll(getDataSelector(dataSelector))); + }, + + /** + * Get scrollable parent element + * @method getScrollableParent + * @param {HTMLElement} element + * @return {HTMLElement} + * @static + * @member ns.util.selectors + */ + getScrollableParent: function (element) { + var overflow, + style; + + while (element && element !== document.body) { + style = window.getComputedStyle(element); + + if (style) { + overflow = style.getPropertyValue("overflow-y"); + if (overflow === "scroll" || (overflow === "auto" && + element.scrollHeight > element.clientHeight)) { + return element; + } + } + element = element.parentNode; + } + return null; + } + }; + }(window.document, ns)); + +/*global define, ns */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (c) 2010 - 2014 Samsung Electronics Co., Ltd. + * License : MIT License V2 + */ +/** + * #Object Utility + * Object contains functions help work with objects. + * @class ns.util.object + * @author Maciej Urbanski + * @author Piotr Karny + */ +(function () { + "use strict"; + + var object = { + /** + * Copy object to new object + * @method copy + * @param {Object} orgObject + * @return {Object} + * @static + * @member ns.util.object + */ + copy: function (orgObject) { + return object.merge({}, orgObject); + }, + + /** + * Attach fields from second object to first object. + * @method fastMerge + * @param {Object} newObject + * @param {Object} orgObject + * @return {Object} + * @static + * @member ns.util.object + */ + fastMerge: function (newObject, orgObject) { + var key; + + for (key in orgObject) { + if (orgObject.hasOwnProperty(key)) { + newObject[key] = orgObject[key]; + } + } + return newObject; + }, + + /** + * Attach fields from second and next object to first object. + * @method merge + * @return {Object} + * @static + * @member ns.util.object + */ + merge: function (/* newObject, orgObject, override */) { + var newObject, + orgObject, + override, + key, + args = [].slice.call(arguments), + argsLength = args.length, + i; + + newObject = args.shift(); + override = true; + if (typeof arguments[argsLength - 1] === "boolean") { + override = arguments[argsLength - 1]; + argsLength--; + } + for (i = 0; i < argsLength; i++) { + orgObject = args.shift(); + if (orgObject !== null) { + for (key in orgObject) { + if (orgObject.hasOwnProperty(key) && (override || newObject[key] === undefined)) { + newObject[key] = orgObject[key]; + } + } + } + } + return newObject; + }, + + /** + * Function add to Constructor prototype Base object and add to prototype properties and methods from + * prototype object. + * @method inherit + * @param {Function} Constructor + * @param {Function} Base + * @param {Object} prototype + * @static + * @member ns.util.object + */ + /* jshint -W083 */ + inherit: function (Constructor, Base, prototype) { + var basePrototype = new Base(), + property, + value; + + for (property in prototype) { + if (prototype.hasOwnProperty(property)) { + value = prototype[property]; + if (typeof value === "function") { + basePrototype[property] = (function createFunctionWithSuper(Base, property, value) { + var _super = function () { + var superFunction = Base.prototype[property]; + + if (superFunction) { + return superFunction.apply(this, arguments); + } + return null; + }; + + return function () { + var __super = this._super, + returnValue; + + this._super = _super; + returnValue = value.apply(this, arguments); + this._super = __super; + return returnValue; + }; + }(Base, property, value)); + } else { + basePrototype[property] = value; + } + } + } + + Constructor.prototype = basePrototype; + Constructor.prototype.constructor = Constructor; + }, + + /** + * Returns true if every property value corresponds value from 'value' argument + * @method hasPropertiesOfValue + * @param {Object} obj + * @param {*} [value=undefined] + * @return {boolean} + */ + hasPropertiesOfValue: function (obj, value) { + var keys = Object.keys(obj), + i = keys.length; + + // Empty array should return false + if (i === 0) { + return false; + } + + while (--i >= 0) { + if (obj[keys[i]] !== value) { + return false; + } + } + + return true; + }, + + /** + * Remove properties from object. + * @method removeProperties + * @param {Object} object + * @param {Array} propertiesToRemove + * @return {Object} + */ + removeProperties: function (object, propertiesToRemove) { + var length = propertiesToRemove.length, + property, + i; + + for (i = 0; i < length; i++) { + property = propertiesToRemove[i]; + if (object.hasOwnProperty(property)) { + delete object[property]; + } + } + return object; + } + }; + + ns.util.object = object; + }()); + +/*global window, define, ns, Node */ +/*jslint nomen: true, plusplus: true, bitwise: false */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (c) 2010 - 2014 Samsung Electronics Co., Ltd. + * License : MIT License V2 + */ +/** + * #Engine + * Main class with engine of library which control communication + * between parts of framework. + * @class ns.engine + * @author Maciej Urbanski + * @author Krzysztof Antoszek + * @author Michal Szepielak + * @author Jadwiga Sosnowska + * @author Maciej Moczulski + * @author Piotr Karny + * @author Tomasz Lukawski + * @author Przemyslaw Ciezkowski + * @author Hyunkook, Cho + * @author Hyeoncheol Choi + * @author Piotr Ostalski + */ +(function (window, document) { + "use strict"; + var slice = [].slice, + /** + * @property {Object} eventUtils {@link ns.event} + * @private + * @static + * @member ns.engine + */ + eventUtils = ns.event, + util = ns.util, + objectUtils = util.object, + selectors = util.selectors, + arrayUtils = ns.util.array, + /** + * @property {Object} widgetDefinitions Object with widgets definitions + * @private + * @static + * @member ns.engine + */ + widgetDefinitions = {}, + /** + * @property {Object} widgetBindingMap Object with widgets bindings + * @private + * @static + * @member ns.engine + */ + widgetBindingMap = {}, + location = window.location, + /** + * engine mode, if true then engine only builds widgets + * @property {boolean} justBuild + * @private + * @static + * @member ns.engine + */ + justBuild = location.hash === "#build", + /** + * @property {string} [TYPE_STRING="string"] local cache of string type name + * @private + * @static + * @readonly + * @member ns.engine + */ + TYPE_STRING = "string", + /** + * @property {string} [TYPE_FUNCTION="function"] local cache of function type name + * @private + * @static + * @readonly + * @member ns.engine + */ + TYPE_FUNCTION = "function", + /** + * @property {string} [DATA_BUILT="data-tau-built"] attribute informs that widget id build + * @private + * @static + * @readonly + * @member ns.engine + */ + DATA_BUILT = "data-tau-built", + /** + * @property {string} [DATA_NAME="data-tau-name"] attribute contains widget name + * @private + * @static + * @readonly + * @member ns.engine + */ + DATA_NAME = "data-tau-name", + /** + * @property {string} [DATA_BOUND="data-tau-bound"] attribute informs that widget id bound + * @private + * @static + * @readonly + * @member ns.engine + */ + DATA_BOUND = "data-tau-bound", + /** + * @property {string} NAMES_SEPARATOR + * @private + * @static + * @readonly + */ + NAMES_SEPARATOR = ",", + /** + * @property {string} [querySelectorWidgets="*[data-tau-built][data-tau-name]:not([data-tau-bound])"] query selector for all widgets which are built but not bound + * @private + * @static + * @member ns.engine + */ + querySelectorWidgets = "*[" + DATA_BUILT + "][" + DATA_NAME + "]:not([" + DATA_BOUND + "])", + /** + * @method excludeBuildAndBound + * @private + * @static + * @param {string} widgetType + * @member ns.engine + * @return {string} :not([data-tau-built*='widgetName']):not([data-tau-bound*='widgetName']) + */ + excludeBuiltAndBound = function (widgetType) { + return ":not([" + DATA_BUILT + "*='" + widgetType + "']):not([" + DATA_BOUND + "*='" + widgetType + "'])"; + }, + + /** + * Engine event types + * @property {Object} eventType + * @property {string} eventType.INIT="tauinit" INIT of framework init event + * @property {string} eventType.WIDGET_BOUND="widgetbound" WIDGET_BOUND of widget bound event + * @property {string} eventType.WIDGET_DEFINED="widgetdefined" WIDGET_DEFINED of widget built event + * @property {string} eventType.WIDGET_BUILT="widgetbuilt" WIDGET_BUILT of widget built event + * @property {string} eventType.BOUND="bound" BOUND of bound event + * @static + * @readonly + * @member ns.engine + */ + eventType = { + INIT: "tauinit", + READY: "tauready", + WIDGET_BOUND: "widgetbound", + WIDGET_DEFINED: "widgetdefined", + WIDGET_BUILT: "widgetbuilt", + DESTROY: "taudestroy", + BOUND: "bound", + WIDGET_INIT: "init" + }, + engine; + + /** + * This function prepares selector for widget' definition + * @method selectorChange + * @param {string} selectorName + * @return {string} new selector + * @member ns.engine + * @static + */ + function selectorChange(selectorName) { + if (selectorName.match(/\[data-role=/) && !selectorName.match(/:not\(\[data-role=/)) { + return selectorName.trim(); + } + return selectorName.trim() + ":not([data-role='none'])"; + } + + /** + * Function to define widget + * @method defineWidget + * @param {string} name + * @param {string} selector + * @param {Array} methods + * @param {Object} widgetClass + * @param {string} [namespace] + * @param {boolean} [redefine] + * @param {boolean} [widgetNameToLowercase=true] + * @return {boolean} + * @member ns.engine + * @static + */ + function defineWidget(name, selector, methods, widgetClass, namespace, redefine, widgetNameToLowercase, BaseElement) { + var definition; + // Widget name is absolutely required + + if (name) { + if (!widgetDefinitions[name] || redefine) { + methods = methods || []; + methods.push("destroy", "disable", "enable", "option", "refresh", "value"); + definition = { + name: name, + methods: methods, + selector: selector || "", + selectors: selector ? selector.split(",").map(selectorChange) : [], + widgetClass: widgetClass || null, + namespace: namespace || "", + widgetNameToLowercase: widgetNameToLowercase === undefined ? true : !!widgetNameToLowercase, + BaseElement: BaseElement + }; + + widgetDefinitions[name] = definition; + if (namespace) { + widgetDefinitions[namespace + "." + name] = definition; + } + eventUtils.trigger(document, "widgetdefined", definition, false); + return true; + } + } else { + ns.error("Widget with selector [" + selector + "] defined without a name, aborting!"); + } + return false; + } + + + /** + * Get widget instance from binding for given element and type + * @method getInstanceByElement + * @static + * @param {Object} binding + * @param {HTMLElement} element + * @param {string} [type] widget name, if is empty then return first built widget + * @return {?Object} + * @member ns.engine + */ + function getInstanceByElement(binding, element, type) { + var widgetInstance, + bindingElement, + storedWidgetNames, + names = type ? type.split(".") : [], + name = names.pop(), + namespace = names.pop(); + + // If name is defined it's possible to fetch it instantly + if (name) { + widgetInstance = binding.instances[name]; + } else { + storedWidgetNames = Object.keys(binding.instances); + widgetInstance = binding.instances[storedWidgetNames[0]]; + } + + if (namespace && widgetInstance && widgetInstance.namespace !== namespace) { + widgetInstance = null; + } + + // Return only it instance of the proper widget exists + if (widgetInstance) { + + // Check if widget instance has that same object referenced + if (widgetInstance.element === element) { + return widgetInstance; + } + } + + return null; + } + + /** + * Get binding for element + * @method getBinding + * @static + * @param {HTMLElement|string} element + * @param {string} [type] widget name + * @return {?Object} + * @member ns.engine + */ + function getBinding(element, type) { + var id = !element || typeof element === TYPE_STRING ? element : element.id, + binding; + + if (typeof element === TYPE_STRING) { + element = document.getElementById(id); + } + + if (element) { + // Fetch group of widget defined for this element + binding = widgetBindingMap[id]; + + if (binding && typeof binding === "object") { + return getInstanceByElement(binding, element, type); + } + } + + return null; + } + + /** + * Set binding of widget + * @method setBinding + * @param {ns.widget.BaseWidget} widgetInstance + * @static + * @member ns.engine + */ + function setBinding(widgetInstance) { + var id = widgetInstance.element.id, + type = widgetInstance.name, + widgetBinding = widgetBindingMap[id]; + + + // If the HTMLElement never had a widget declared create an empty object + if (!widgetBinding) { + widgetBinding = { + elementId: id, + element: widgetInstance.element, + instances: {} + }; + } + + widgetBinding.instances[type] = widgetInstance; + widgetBindingMap[id] = widgetBinding; + } + + /** + * Returns all bindings for element or id gives as parameter + * @method getAllBindings + * @param {HTMLElement|string} element + * @return {?Object} + * @static + * @member ns.engine + */ + function getAllBindings(element) { + var id = !element || typeof element === TYPE_STRING ? element : element.id; + + return (widgetBindingMap[id] && widgetBindingMap[id].instances) || null; + } + + /** + * Removes given name from attributeValue string. + * Names should be separated with a NAMES_SEPARATOR + * @param {string} name + * @param {string} attributeValue + * @private + * @static + * @return {string} + */ + function _removeWidgetNameFromAttribute(name, attributeValue) { + var widgetNames, + searchResultIndex; + + // Split attribute value by separator + widgetNames = attributeValue.split(NAMES_SEPARATOR); + searchResultIndex = widgetNames.indexOf(name); + + if (searchResultIndex > -1) { + widgetNames.splice(searchResultIndex, 1); + attributeValue = widgetNames.join(NAMES_SEPARATOR); + } + + return attributeValue; + } + + function _removeAllBindingAttributes(element) { + element.removeAttribute(DATA_BUILT); + element.removeAttribute(DATA_BOUND); + element.removeAttribute(DATA_NAME); + } + + /** + * Remove binding data attributes for element. + * @method _removeBindingAttributes + * @param {HTMLElement} element + * @param {string} type widget type (name) + * @private + * @static + * @member ns.engine + */ + function _removeWidgetFromAttributes(element, type) { + var dataBuilt, + dataBound, + dataName; + + // Most often case is that name is not defined + if (!type) { + _removeAllBindingAttributes(element); + } else { + dataBuilt = _removeWidgetNameFromAttribute(type, element.getAttribute(DATA_BUILT) || ""); + dataBound = _removeWidgetNameFromAttribute(type, element.getAttribute(DATA_BOUND) || ""); + dataName = _removeWidgetNameFromAttribute(type, element.getAttribute(DATA_NAME) || ""); + + // Check if all attributes have at least one widget + if (dataBuilt && dataBound && dataName) { + element.setAttribute(DATA_BUILT, dataBuilt); + element.setAttribute(DATA_BOUND, dataBound); + element.setAttribute(DATA_NAME, dataName); + } else { + // If something is missing remove everything + _removeAllBindingAttributes(element); + } + } + } + + /** + * Method removes binding for single widget. + * @method _removeSingleBinding + * @param {Object} bindingGroup + * @param {string} type + * @return {boolean} + * @private + * @static + */ + function _removeSingleBinding(bindingGroup, type) { + var widgetInstance = bindingGroup[type]; + + if (widgetInstance) { + if (widgetInstance.element && typeof widgetInstance.element.setAttribute === TYPE_FUNCTION) { + _removeWidgetFromAttributes(widgetInstance.element, type); + } + + delete bindingGroup[type]; + + return true; + } + + return false; + } + + /** + * Remove group of bindings for all types of widgets based on the same element + * @method removeGroupBindingAllTypes + * @param {Object} bindingGroup + * @param {string} id widget element id + * @return {boolean} + * @static + * @member ns.engine + */ + function removeGroupBindingAllTypes(bindingGroup, id) { + var singleSuccess, + widgetName, + fullSuccess = true; + + // Iterate over group of created widgets + for (widgetName in bindingGroup) { + if (bindingGroup.hasOwnProperty(widgetName)) { + singleSuccess = _removeSingleBinding(bindingGroup, widgetName); + + // As we iterate over keys we are sure we want to remove this element + // NOTE: Removing property by delete is slower than assigning null value + bindingGroup[widgetName] = null; + + fullSuccess = (fullSuccess && singleSuccess); + } + } + + // If the object bindingGroup is empty or every key has a null value + if (objectUtils.hasPropertiesOfValue(bindingGroup, null)) { + // NOTE: Removing property by delete is slower than assigning null value + widgetBindingMap[id] = null; + } + + return fullSuccess; + } + + /** + * Remove group of bindings for widgets based on the same element + * @method removeGroupBinding + * @param {Object} bindingGroup + * @param {string} type object name + * @param {string} id widget element id + * @return {boolean} + * @static + * @member ns.engine + */ + function removeGroupBinding(bindingGroup, type, id) { + var success; + + if (!type) { + success = removeGroupBindingAllTypes(bindingGroup, id); + } else { + success = _removeSingleBinding(bindingGroup, type); + if (objectUtils.hasPropertiesOfValue(bindingGroup, null)) { + widgetBindingMap[id] = null; + } + } + return success; + } + + /** + * Remove binding for widget based on element. + * @method removeBinding + * @param {HTMLElement|string} element + * @param {?string} [type=null] widget name + * @return {boolean} + * @static + * @member ns.engine + */ + function removeBinding(element, type) { + var id = (typeof element === TYPE_STRING) ? element : element.id, + binding = widgetBindingMap[id], + bindingGroup; + + // [NOTICE] Due to backward compatibility calling removeBinding + // with one parameter should remove all bindings + + if (binding) { + if (typeof element === TYPE_STRING) { + // Search based on current document may return bad results, + // use previously defined element if it exists + element = binding.element; + } + + if (element) { + _removeWidgetFromAttributes(element, type); + } + + bindingGroup = widgetBindingMap[id] && widgetBindingMap[id].instances; + if (bindingGroup) { + return removeGroupBinding(bindingGroup, type, id); + } + + if (widgetBindingMap[id].instances && (Object.keys(widgetBindingMap[id].instances).length === 0)) { + widgetBindingMap[id] = null; + } + } + + return false; + } + + /** + * Removes all bindings of widgets. + * @method removeAllBindings + * @param {HTMLElement|string} element + * @return {boolean} + * @static + * @member ns.engine + */ + function removeAllBindings(element) { + // @TODO this should be coded in the other way around, removeAll should loop through all bindings and inside call removeBinding + // but due to backward compatibility that code should be more readable + return removeBinding(element); + } + + /** + * If element not exist create base element for widget. + * @method ensureElement + * @param {HTMLElement} element + * @param {ns.widget.BaseWidget} Widget + * @return {HTMLElement} + * @static + * @private + * @member ns.engine + */ + function ensureElement(element, Widget) { + if (!element || !(element instanceof HTMLElement)) { + if (typeof Widget.createEmptyElement === TYPE_FUNCTION) { + element = Widget.createEmptyElement(); + } else { + element = document.createElement("div"); + } + } + return element; + } + + /** + * Process core widget method + * - configure + * - build + * - init + * - bindEvents + * @method processWidget + * @param {HTMLElement} element base element of widget + * @param {Object} widgetInstance instance of widget + * @param {Object} definition definition of widget + * @param {ns.widget.BaseWidget} definition.widgetClass + * @param {string} definition.name + * @param {Object} [options] options for widget + * @private + * @static + * @member ns.engine + */ + function coreProcessWidget(element, widgetInstance, definition, options) { + var widgetOptions = options || {}, + createFunction = widgetOptions.create, + buildAttribute; + + + widgetInstance.configure(definition, element, options); + + // Run .create method from widget options when a [widgetName]create event is triggered + if (typeof createFunction === TYPE_FUNCTION) { + eventUtils.one(element, definition.name.toLowerCase() + "create", createFunction); + } + + if (element.id) { + widgetInstance.id = element.id; + } + + // Check if this type of widget was build for this element before + buildAttribute = element.getAttribute(DATA_BUILT); + if (!buildAttribute || + buildAttribute.split(NAMES_SEPARATOR).indexOf(widgetInstance.name) === -1) { + element = widgetInstance.build(element); + } + + if (element) { + widgetInstance.element = element; + + setBinding(widgetInstance); + + widgetInstance.trigger(eventType.WIDGET_BUILT, widgetInstance, false); + + if (!justBuild) { + widgetInstance.init(element); + } + + widgetInstance.bindEvents(element, justBuild); + + widgetInstance.trigger(widgetInstance.widgetEventPrefix + eventType.WIDGET_INIT); + widgetInstance.trigger(eventType.WIDGET_BOUND, widgetInstance, false); + eventUtils.trigger(document, eventType.WIDGET_BOUND, widgetInstance); + } else { + } + } + + /** + * Load widget + * @method processWidget + * @param {HTMLElement} element base element of widget + * @param {Object} definition definition of widget + * @param {ns.widget.BaseWidget} definition.widgetClass + * @param {string} definition.name + * @param {Object} [options] options for widget + * @return {?HTMLElement} + * @private + * @static + * @member ns.engine + */ + function processWidget(element, definition, options) { + var Widget = definition.widgetClass, + /** + * @type {ns.widget.BaseWidget} widgetInstance + */ + widgetInstance, + parentEnhance, + existingBinding; + + element = ensureElement(element, Widget); + widgetInstance = Widget ? new Widget(element) : false; + + // if any parent has attribute data-enhance=false then stop building widgets + parentEnhance = selectors.getParentsBySelectorNS(element, "enhance=false"); + + // While processing widgets queue other widget may built this one before + // it reaches it's turn + existingBinding = getBinding(element, definition.name); + if (existingBinding && existingBinding.element === element) { + return element; + } + + if (widgetInstance) { + if (!parentEnhance.length) { + coreProcessWidget(element, widgetInstance, definition, options); + } + return widgetInstance.element; + } + + return null; + } + + /** + * Destroys widget of given 'type' for given HTMLElement. + * [NOTICE] This method won't destroy any children widgets. + * @method destroyWidget + * @param {HTMLElement|string} element + * @param {string} type + * @static + * @member ns.engine + */ + function destroyWidget(element, type) { + var widgetInstance; + + if (typeof element === TYPE_STRING) { + element = document.getElementById(element); + } + + + // If type is not defined all widgets should be removed + // this is for backward compatibility + widgetInstance = getBinding(element, type); + + if (widgetInstance) { + //Destroy widget + widgetInstance.destroy(); + widgetInstance.trigger("widgetdestroyed"); + + removeBinding(element, type); + } + } + + /** + * Calls destroy on group of widgets connected with given HTMLElement + * @method destroyGroupWidgets + * @param {HTMLElement|string} element + * @static + * @private + * @member ns.engine + */ + function destroyGroupWidgets(element) { + var widgetName, + widgetInstance, + widgetGroup; + + widgetGroup = getAllBindings(element); + for (widgetName in widgetGroup) { + if (widgetGroup.hasOwnProperty(widgetName)) { + widgetInstance = widgetGroup[widgetName]; + + //Destroy widget + if (widgetInstance) { + widgetInstance.destroy(); + widgetInstance.trigger("widgetdestroyed"); + } + } + } + } + + /** + * Calls destroy on widget (or widgets) connected with given HTMLElement + * Removes child widgets as well. + * @method destroyAllWidgets + * @param {HTMLElement|string} element + * @param {boolean} [childOnly=false] destroy only widgets on children elements + * @static + * @member ns.engine + */ + function destroyAllWidgets(element, childOnly) { + var childWidgets, + i; + + if (typeof element === TYPE_STRING) { + element = document.getElementById(element); + } + + + if (!childOnly) { + // If type is not defined all widgets should be removed + // this is for backward compatibility + destroyGroupWidgets(element); + } + + //Destroy child widgets, if something left. + childWidgets = slice.call(element.querySelectorAll("[" + DATA_BOUND + "]")); + for (i = childWidgets.length - 1; i >= 0; i -= 1) { + if (childWidgets[i]) { + destroyAllWidgets(childWidgets[i], false); + } + } + + removeAllBindings(element); + } + + /** + * Load widgets from data-* definition + * @method processHollowWidget + * @param {HTMLElement} element base element of widget + * @param {Object} definition widget definition + * @param {Object} [options] options for create widget + * @return {HTMLElement} base element of widget + * @private + * @static + * @member ns.engine + */ + function processHollowWidget(element, definition, options) { + var name = (element && element.getAttribute(DATA_NAME)) || + (definition && definition.name); + definition = definition || (name && widgetDefinitions[name]) || { + "name": name + }; + return processWidget(element, definition, options); + } + + /** + * Compare function for nodes on build queue + * @param {Object} nodeA + * @param {Object} nodeB + * @return {number} + * @private + * @static + */ + function compareByDepth(nodeA, nodeB) { + /*jshint -W016 */ + var mask = Node.DOCUMENT_POSITION_CONTAINS | Node.DOCUMENT_POSITION_PRECEDING; + + if (nodeA.element === nodeB.element) { + return 0; + } + + if (nodeA.element.compareDocumentPosition(nodeB.element) & mask) { + return 1; + } + /*jshint +W016 */ + return -1; + } + + /** + * Processes one build queue item. Runs processHollowWidget + * underneath + * @method processBuildQueueItem + * @param {Object|HTMLElement} queueItem + * @private + * @static + */ + function processBuildQueueItem(queueItem) { + // HTMLElement doesn't have .element property + // widgetDefinitions will return undefined when called widgetDefinitions[undefined] + processHollowWidget(queueItem.element || queueItem, widgetDefinitions[queueItem.widgetName]); + } + + /** + * Build widgets on all children of context element + * @method createWidgets + * @static + * @param {HTMLElement} context base html for create children + * @member ns.engine + */ + function createWidgets(context) { + // find widget which are built + var builtWidgetElements = slice.call(context.querySelectorAll(querySelectorWidgets)), + normal, + buildQueue = [], + selectorKeys = Object.keys(widgetDefinitions), + excludeSelector, + i, + j, + len = selectorKeys.length, + definition, + widgetName, + definitionSelectors; + + + + // process built widgets + builtWidgetElements.forEach(processBuildQueueItem); + + // process widgets didn't build + for (i = 0; i < len; ++i) { + widgetName = selectorKeys[i]; + if (widgetName.indexOf(".") === -1) { + definition = widgetDefinitions[widgetName]; + definitionSelectors = definition.selectors; + if (definitionSelectors.length) { + excludeSelector = excludeBuiltAndBound(widgetName); + + normal = slice.call(context.querySelectorAll(definitionSelectors.join(excludeSelector + ",") + + excludeSelector)); + j = normal.length; + + while (--j >= 0) { + buildQueue.push({ + element: normal[j], + widgetName: widgetName + }); + } + } + } + } + + // Sort queue by depth, on every DOM branch outer most element go first + buildQueue.sort(compareByDepth); + + // Build all widgets from queue + buildQueue.forEach(processBuildQueueItem); + + + eventUtils.trigger(document, "built"); + eventUtils.trigger(document, eventType.BOUND); + } + + /** + * Handler for event create + * @method createEventHandler + * @param {Event} event + * @static + * @member ns.engine + */ + function createEventHandler(event) { + createWidgets(event.target); + } + + function setViewport() { + /** + * Sets viewport tag if not exists + */ + var documentHead = document.head, + metaTagListLength, + metaTagList, + metaTag, + i; + + metaTagList = documentHead.querySelectorAll("[name=\"viewport\"]"); + metaTagListLength = metaTagList.length; + + if (metaTagListLength > 0) { + // Leave the last viewport tag + --metaTagListLength; + + // Remove duplicated tags + for (i = 0; i < metaTagListLength; ++i) { + // Remove meta tag from DOM + documentHead.removeChild(metaTagList[i]); + } + } else { + // Create new HTML Element + metaTag = document.createElement("meta"); + + // Set required attributes + metaTag.setAttribute("name", "viewport"); + metaTag.setAttribute("content", "width=device-width, user-scalable=no"); + + // Force that viewport tag will be first child of head + if (documentHead.firstChild) { + documentHead.insertBefore(metaTag, documentHead.firstChild); + } else { + documentHead.appendChild(metaTag); + } + } + } + + /** + * Build first page + * @method build + * @static + * @member ns.engine + */ + function build() { + eventUtils.trigger(document, eventType.READY); + setViewport(); + } + + /** + * Method to remove all listeners bound in run + * @method stop + * @static + * @member ns.engine + */ + function stop() { + } + + /** + * Method to remove all listeners bound in run + * @method destroy + * @static + * @member ns.engine + */ + function destroy() { + stop(); + eventUtils.fastOff(document, "create", createEventHandler); + destroyAllWidgets(document.body, true); + eventUtils.trigger(document, eventType.DESTROY); + } + + /** + * Add to object value at index equal to type of arg. + * @method getType + * @param {Object} result + * @param {*} arg + * @return {Object} + * @static + * @private + * @member ns.engine + */ + function getType(result, arg) { + var type = arg instanceof HTMLElement ? "HTMLElement" : typeof arg; + + result[type] = arg; + return result; + } + + /** + * Convert args array to object with keys being types and arguments mapped by values + * @method getArgumentsTypes + * @param {Arguments[]} args + * @return {Object} + * @static + * @private + * @member ns.engine + */ + function getArgumentsTypes(args) { + return arrayUtils.reduce(args, getType, {}); + } + + ns.widgetDefinitions = {}; + engine = { + justBuild: location.hash === "#build", + /** + * object with names of engine attributes + * @property {Object} dataTau + * @property {string} [dataTau.built="data-tau-built"] attribute inform that widget id build + * @property {string} [dataTau.name="data-tau-name"] attribute contains widget name + * @property {string} [dataTau.bound="data-tau-bound"] attribute inform that widget id bound + * @property {string} [dataTau.separator=","] separation string for widget names + * @static + * @member ns.engine + */ + dataTau: { + built: DATA_BUILT, + name: DATA_NAME, + bound: DATA_BOUND, + separator: NAMES_SEPARATOR + }, + destroyWidget: destroyWidget, + destroyAllWidgets: destroyAllWidgets, + createWidgets: createWidgets, + + /** + * Method to get all definitions of widgets + * @method getDefinitions + * @return {Object} + * @static + * @member ns.engine + */ + getDefinitions: function () { + return widgetDefinitions; + }, + /** + * Returns definition of widget + * @method getWidgetDefinition + * @param {string} name + * @static + * @member ns.engine + * @return {Object} + */ + getWidgetDefinition: function (name) { + return widgetDefinitions[name]; + }, + defineWidget: defineWidget, + getBinding: getBinding, + getAllBindings: getAllBindings, + setBinding: setBinding, + removeBinding: removeBinding, + removeAllBindings: removeAllBindings, + + /** + * Clear bindings of widgets + * @method _clearBindings + * @static + * @member ns.engine + */ + _clearBindings: function () { + //clear and set references to the same object + widgetBindingMap = {}; + }, + + build: build, + + /** + * Run engine + * @method run + * @static + * @member ns.engine + */ + run: function () { + stop(); + + eventUtils.fastOn(document, "create", createEventHandler); + + eventUtils.trigger(document, eventType.INIT, {tau: ns}); + + switch (document.readyState) { + case "interactive": + case "complete": + build(); + break; + default: + eventUtils.one(document, "DOMContentLoaded", build.bind(engine)); + break; + } + }, + + /** + * Build instance of widget and binding events + * Returns error when empty element is passed + * @method instanceWidget + * @param {HTMLElement|string} [element] + * @param {string} name + * @param {Object} [options] + * @return {?Object} + * @static + * @member ns.engine + */ + instanceWidget: function (element, name, options) { + var binding, + definition, + argumentsTypes = getArgumentsTypes(arguments); + + // Map arguments with specific types to correct variables + // Only name is required argument + element = argumentsTypes.HTMLElement; + name = argumentsTypes.string; + options = argumentsTypes.object; + // If element exists try to find existing binding + if (element) { + binding = getBinding(element, name); + } + // If didn't found binding build new widget + if (!binding && widgetDefinitions[name]) { + definition = widgetDefinitions[name]; + element = processHollowWidget(element, definition, options); + binding = getBinding(element, name); + } else if (binding) { + // if widget was built early we should set options delivered to constructor + binding.option(options); + } + return binding; + }, + + stop: stop, + + destroy: destroy, + + /** + * Method to change build mode + * @method setJustBuild + * @param {boolean} newJustBuild + * @static + * @member ns.engine + */ + setJustBuild: function (newJustBuild) { + // Set location hash to have a consistent behavior + if (newJustBuild) { + location.hash = "build"; + } else { + location.hash = ""; + } + + justBuild = newJustBuild; + }, + + /** + * Method to get build mode + * @method getJustBuild + * @return {boolean} + * @static + * @member ns.engine + */ + getJustBuild: function () { + return justBuild; + }, + _createEventHandler: createEventHandler + }; + + engine.eventType = eventType; + ns.engine = engine; + }(window, window.document)); + +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/*global window, define, ns */ +/** + * #Utility DOM + * Utility object with function to DOM manipulation, CSS properties support + * and DOM attributes support. + * + * # How to replace jQuery methods by ns methods + * ## append vs appendNodes + * + * #### HTML code before manipulation + * + * @example + *
+ *
Hello
+ *
And
+ *
Goodbye
+ *
+ * + * #### jQuery manipulation + * + * @example + * $( "#second" ).append( "Test" ); + + * #### ns manipulation + * + * @example + * var context = document.getElementById("second"), + * element = document.createElement("span"); + * element.innerHTML = "Test"; + * ns.util.DOM.appendNodes(context, element); + * + * #### HTML code after manipulation + * + * @example + *
+ *
Hello
+ *
And + * Test + *
+ *
Goodbye
+ *
+ * + * ## replaceWith vs replaceWithNodes + * + * #### HTML code before manipulation + * + * @example + *
+ *
Hello
+ *
And
+ *
Goodbye
+ *
+ * + * #### jQuery manipulation + * + * @example + * $('#second').replaceWith("Test"); + * + * #### ns manipulation + * + * @example + * var context = document.getElementById("second"), + * element = document.createElement("span"); + * element.innerHTML = "Test"; + * ns.util.DOM.replaceWithNodes(context, element); + * + * #### HTML code after manipulation + * + * @example + *
+ *
Hello
+ * Test + *
Goodbye
+ *
+ * + * ## before vs insertNodesBefore + * + * #### HTML code before manipulation + * + * @example + *
+ *
Hello
+ *
And
+ *
Goodbye
+ *
+ * + * #### jQuery manipulation + * + * @example + * $( "#second" ).before( "Test" ); + * + * #### ns manipulation + * + * @example + * var context = document.getElementById("second"), + * element = document.createElement("span"); + * element.innerHTML = "Test"; + * ns.util.DOM.insertNodesBefore(context, element); + * + * #### HTML code after manipulation + * + * @example + *
+ *
Hello
+ * Test + *
And
+ *
Goodbye
+ *
+ * + * ## wrapInner vs wrapInHTML + * + * #### HTML code before manipulation + * + * @example + *
+ *
Hello
+ *
And
+ *
Goodbye
+ *
+ * + * #### jQuery manipulation + * + * @example + * $( "#second" ).wrapInner( "" ); + * + * #### ns manipulation + * + * @example + * var element = document.getElementById("second"); + * ns.util.DOM.wrapInHTML(element, ""); + * + * #### HTML code after manipulation + * + * @example + *
+ *
Hello
+ *
+ * And + *
+ *
Goodbye
+ *
+ * + * @class ns.util.DOM + * @author Jadwiga Sosnowska + * @author Krzysztof Antoszek + * @author Maciej Moczulski + * @author Piotr Karny + */ +(function () { + "use strict"; + ns.util.DOM = ns.util.DOM || {}; + }()); + +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/*global window, ns, define */ +/* + * @author Jadwiga Sosnowska + * @author Krzysztof Antoszek + * @author Maciej Moczulski + * @author Piotr Karny + */ +(function () { + "use strict"; + + + var selectors = ns.util.selectors, + DOM = ns.util.DOM, + NAMESPACE = "namespace"; + + /** + * Returns given attribute from element or the closest parent, + * which matches the selector. + * @method inheritAttr + * @member ns.util.DOM + * @param {HTMLElement} element + * @param {string} attr + * @param {string} selector + * @return {?string} + * @static + */ + DOM.inheritAttr = function (element, attr, selector) { + var value = element.getAttribute(attr), + parent; + + if (!value) { + parent = selectors.getClosestBySelector(element, selector); + if (parent) { + return parent.getAttribute(attr); + } + } + return value; + }; + + /** + * Returns Number from properties described in html tag + * @method getNumberFromAttribute + * @member ns.util.DOM + * @param {HTMLElement} element + * @param {string} attribute + * @param {string=} [type] auto type casting + * @param {number} [defaultValue] default returned value + * @static + * @return {number} + */ + DOM.getNumberFromAttribute = function (element, attribute, type, defaultValue) { + var value = element.getAttribute(attribute), + result = defaultValue; + + if (!isNaN(value)) { + if (type === "float") { + value = parseFloat(value); + if (!isNaN(value)) { + result = value; + } + } else { + value = parseInt(value, 10); + if (!isNaN(value)) { + result = value; + } + } + } + return result; + }; + + function getDataName(name, skipData) { + var _namespace = ns.getConfig(NAMESPACE), + prefix = ""; + + if (!skipData) { + prefix = "data-"; + } + return prefix + (_namespace ? _namespace + "-" : "") + name; + } + + /** + * Special function to set attribute and property in the same time + * @method setAttribute + * @param {HTMLElement} element + * @param {string} name + * @param {Mixed} value + * @member ns.util.DOM + * @static + */ + function setAttribute(element, name, value) { + element[name] = value; + element.setAttribute(name, value); + } + + /** + * This function sets value of attribute data-{namespace}-{name} for element. + * If the namespace is empty, the attribute data-{name} is used. + * @method setNSData + * @param {HTMLElement} element Base element + * @param {string} name Name of attribute + * @param {string|number|boolean} value New value + * @member ns.util.DOM + * @static + */ + DOM.setNSData = function (element, name, value) { + element.setAttribute(getDataName(name), value); + }; + + /** + * This function returns value of attribute data-{namespace}-{name} for element. + * If the namespace is empty, the attribute data-{name} is used. + * Method may return boolean in case of 'true' or 'false' strings as attribute value. + * @method getNSData + * @param {HTMLElement} element Base element + * @param {string} name Name of attribute + * @param {boolean} skipData + * @member ns.util.DOM + * @return {?string|boolean} + * @static + */ + DOM.getNSData = function (element, name, skipData) { + var value = element.getAttribute(getDataName(name, skipData)); + + if (value === "true") { + return true; + } + + if (value === "false") { + return false; + } + + return value; + }; + + /** + * This function returns true if attribute data-{namespace}-{name} for element is set + * or false in another case. If the namespace is empty, attribute data-{name} is used. + * @method hasNSData + * @param {HTMLElement} element Base element + * @param {string} name Name of attribute + * @member ns.util.DOM + * @return {boolean} + * @static + */ + DOM.hasNSData = function (element, name) { + return element.hasAttribute(getDataName(name)); + }; + + /** + * Get or set value on data attribute. + * @method nsData + * @param {HTMLElement} element + * @param {string} name + * @param {?Mixed} [value] + * @static + * @member ns.util.DOM + */ + DOM.nsData = function (element, name, value) { + if (value === undefined) { + return DOM.getNSData(element, name); + } else { + return DOM.setNSData(element, name, value); + } + }; + + /** + * This function removes attribute data-{namespace}-{name} from element. + * If the namespace is empty, attribute data-{name} is used. + * @method removeNSData + * @param {HTMLElement} element Base element + * @param {string} name Name of attribute + * @member ns.util.DOM + * @static + */ + DOM.removeNSData = function (element, name) { + element.removeAttribute(getDataName(name)); + }; + + /** + * Returns object with all data-* attributes of element + * @method getData + * @param {HTMLElement} element Base element + * @member ns.util.DOM + * @return {Object} + * @static + */ + DOM.getData = function (element) { + var dataPrefix = "data-", + data = {}, + attributes = element.attributes, + attribute, + nodeName, + value, + i, + length = attributes.length, + lowerCaseValue; + + for (i = 0; i < length; i++) { + attribute = attributes.item(i); + nodeName = attribute.nodeName; + if (nodeName.indexOf(dataPrefix) > -1) { + value = attribute.value; + lowerCaseValue = value.toLowerCase(); + if (lowerCaseValue === "true") { + value = true; + } else if (lowerCaseValue === "false") { + value = false; + } + data[nodeName.replace(dataPrefix, "")] = value; + } + } + + return data; + }; + + /** + * Special function to remove attribute and property in the same time + * @method removeAttribute + * @param {HTMLElement} element + * @param {string} name + * @member ns.util.DOM + * @static + */ + DOM.removeAttribute = function (element, name) { + element.removeAttribute(name); + element[name] = false; + }; + + DOM.setAttribute = setAttribute; + /** + * Special function to set attributes and properties in the same time + * @method setAttribute + * @param {HTMLElement} element + * @param {Object} values + * @member ns.util.DOM + * @static + */ + DOM.setAttributes = function (element, values) { + var i, + names = Object.keys(values), + name, + len; + + for (i = 0, len = names.length; i < len; i++) { + name = names[i]; + setAttribute(element, name, values[name]); + } + }; + }()); + +/*global window, ns, define, RegExp */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * #Path Utility + * Object helps work with paths. + * @class ns.util.path + * @static + * @author Tomasz Lukawski + * @author Maciej Urbanski + * @author Piotr Karny + */ +(function (window, document, ns) { + "use strict"; + var utilsObject = ns.util.object, + /** + * Local alias for ns.util.selectors + * @property {Object} utilsSelectors Alias for {@link ns.util.selectors} + * @member ns.util.path + * @static + * @private + */ + utilsSelectors = ns.util.selectors, + /** + * Local alias for ns.util.DOM + * @property {Object} utilsDOM Alias for {@link ns.util.DOM} + * @member ns.util.path + * @static + * @private + */ + utilsDOM = ns.util.DOM, + /** + * Cache for document base element + * @member ns.util.path + * @property {HTMLBaseElement} base + * @static + * @private + */ + base, + /** + * location object + * @property {Object} location + * @static + * @private + * @member ns.util.path + */ + location = {}, + path = { + /** + * href part for mark state + * @property {string} [uiStateKey="&ui-state"] + * @static + * @member ns.util.path + */ + uiStateKey: "&ui-state", + + // This scary looking regular expression parses an absolute URL or its relative + // variants (protocol, site, document, query, and hash), into the various + // components (protocol, host, path, query, fragment, etc that make up the + // URL as well as some other commonly used sub-parts. When used with RegExp.exec() + // or String.match, it parses the URL into a results array that looks like this: + // + // [0]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread# + // msg-content?param1=true¶m2=123 + // [1]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread + // [2]: http://jblas:password@mycompany.com:8080/mail/inbox + // [3]: http://jblas:password@mycompany.com:8080 + // [4]: http: + // [5]: // + // [6]: jblas:password@mycompany.com:8080 + // [7]: jblas:password + // [8]: jblas + // [9]: password + // [10]: mycompany.com:8080 + // [11]: mycompany.com + // [12]: 8080 + // [13]: /mail/inbox + // [14]: /mail/ + // [15]: inbox + // [16]: ?msg=1234&type=unread + // [17]: #msg-content?param1=true¶m2=123 + // [18]: #msg-content + // [19]: ?param1=true¶m2=123 + // + /** + * @property {RegExp} urlParseRE Regular expression for parse URL + * @member ns.util.path + * @static + */ + urlParseRE: /^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)((#[^\?]*)(\?.*)?)?/, + + /** + * Abstraction to address xss (Issue #4787) by removing the authority in + * browsers that auto decode it. All references to location.href should be + * replaced with a call to this method so that it can be dealt with properly here + * @method getLocation + * @param {string|Object} [url] + * @return {string} + * @member ns.util.path + */ + getLocation: function (url) { + var uri = this.parseUrl(url || window.location.href), + hash = uri.hash, + search = uri.hashSearch; + // mimic the browser with an empty string when the hash and hashSearch are empty + + hash = hash === "#" && !search ? "" : hash; + location = uri; + // Make sure to parse the url or the location object for the hash because using + // location.hash is automatically decoded in firefox, the rest of the url should be from the + // object (location unless we're testing) to avoid the inclusion of the authority + return uri.protocol + "//" + uri.host + uri.pathname + uri.search + hash + search; + }, + + /** + * Return the original document url + * @method getDocumentUrl + * @member ns.util.path + * @param {boolean} [asParsedObject=false] + * @return {string|Object} + * @static + */ + getDocumentUrl: function (asParsedObject) { + return asParsedObject ? utilsObject.copy(path.documentUrl) : path.documentUrl.href; + }, + + /** + * Parse a location into a structure + * @method parseLocation + * @return {Object} + * @member ns.util.path + */ + parseLocation: function () { + return this.parseUrl(this.getLocation()); + }, + + /** + * Parse a URL into a structure that allows easy access to + * all of the URL components by name. + * If we're passed an object, we'll assume that it is + * a parsed url object and just return it back to the caller. + * @method parseUrl + * @member ns.util.path + * @param {string|Object} url + * @return {Object} uri record + * @return {string} return.href + * @return {string} return.hrefNoHash + * @return {string} return.hrefNoSearch + * @return {string} return.domain + * @return {string} return.protocol + * @return {string} return.doubleSlash + * @return {string} return.authority + * @return {string} return.username + * @return {string} return.password + * @return {string} return.host + * @return {string} return.hostname + * @return {string} return.port + * @return {string} return.pathname + * @return {string} return.directory + * @return {string} return.filename + * @return {string} return.search + * @return {string} return.hash + * @return {string} return.hashSearch + * @static + */ + parseUrl: function (url) { + var matches; + + if (typeof url === "object") { + return url; + } + matches = path.urlParseRE.exec(url || "") || []; + + // Create an object that allows the caller to access the sub-matches + // by name. Note that IE returns an empty string instead of undefined, + // like all other browsers do, so we normalize everything so its consistent + // no matter what browser we're running on. + return { + href: matches[0] || "", + hrefNoHash: matches[1] || "", + hrefNoSearch: matches[2] || "", + domain: matches[3] || "", + protocol: matches[4] || "", + doubleSlash: matches[5] || "", + authority: matches[6] || "", + username: matches[8] || "", + password: matches[9] || "", + host: matches[10] || "", + hostname: matches[11] || "", + port: matches[12] || "", + pathname: matches[13] || "", + directory: matches[14] || "", + filename: matches[15] || "", + search: matches[16] || "", + hash: matches[18] || "", + hashSearch: matches[19] || "" + }; + }, + + /** + * Turn relPath into an absolute path. absPath is + * an optional absolute path which describes what + * relPath is relative to. + * @method makePathAbsolute + * @member ns.util.path + * @param {string} relPath + * @param {string} [absPath=""] + * @return {string} + * @static + */ + makePathAbsolute: function (relPath, absPath) { + var absStack, + relStack, + directory, + i; + + if (relPath && relPath.charAt(0) === "/") { + return relPath; + } + + relPath = relPath || ""; + absPath = absPath ? absPath.replace(/^\/|(\/[^\/]*|[^\/]+)$/g, "") : ""; + + absStack = absPath ? absPath.split("/") : []; + relStack = relPath.split("/"); + for (i = 0; i < relStack.length; i++) { + directory = relStack[i]; + switch (directory) { + case ".": + break; + case "..": + if (absStack.length) { + absStack.pop(); + } + break; + default: + absStack.push(directory); + break; + } + } + return "/" + absStack.join("/"); + }, + + /** + * Returns true if both urls have the same domain. + * @method isSameDomain + * @member ns.util.path + * @param {string|Object} absUrl1 + * @param {string|Object} absUrl2 + * @return {boolean} + * @static + */ + isSameDomain: function (absUrl1, absUrl2) { + return path.parseUrl(absUrl1).domain === path.parseUrl(absUrl2).domain; + }, + + /** + * Returns true for any relative variant. + * @method isRelativeUrl + * @member ns.util.path + * @param {string|Object} url + * @return {boolean} + * @static + */ + isRelativeUrl: function (url) { + // All relative Url variants have one thing in common, no protocol. + return path.parseUrl(url).protocol === ""; + }, + + /** + * Returns true for an absolute url. + * @method isAbsoluteUrl + * @member ns.util.path + * @param {string} url + * @return {boolean} + * @static + */ + isAbsoluteUrl: function (url) { + return path.parseUrl(url).protocol !== ""; + }, + + /** + * Turn the specified relative URL into an absolute one. This function + * can handle all relative variants (protocol, site, document, query, fragment). + * @method makeUrlAbsolute + * @member ns.util.path + * @param {string} relUrl + * @param {string} absUrl + * @return {string} + * @static + */ + makeUrlAbsolute: function (relUrl, absUrl) { + var relObj, + absObj, + protocol, + doubleSlash, + authority, + hasPath, + pathname, + search, + hash; + + if (!path.isRelativeUrl(relUrl)) { + return relUrl; + } + + relObj = path.parseUrl(relUrl); + absObj = path.parseUrl(absUrl); + protocol = relObj.protocol || absObj.protocol; + doubleSlash = relObj.protocol ? relObj.doubleSlash : (relObj.doubleSlash || absObj.doubleSlash); + authority = relObj.authority || absObj.authority; + hasPath = relObj.pathname !== ""; + pathname = path.makePathAbsolute(relObj.pathname || absObj.filename, absObj.pathname); + search = relObj.search || (!hasPath && absObj.search) || ""; + hash = relObj.hash; + + return protocol + doubleSlash + authority + pathname + search + hash; + }, + + /** + * Add search (aka query) params to the specified url. + * If page is embedded page, search query will be added after + * hash tag. It's allowed to add query content for both external + * pages and embedded pages. + * Examples: + * http://domain/path/index.html#embedded?search=test + * http://domain/path/external.html?s=query#embedded?s=test + * @method addSearchParams + * @member ns.util.path + * @param {string|Object} url + * @param {Object|string} params + * @return {string} + */ + addSearchParams: function (url, params) { + var urlObject = path.parseUrl(url), + paramsString = (typeof params === "object") ? this.getAsURIParameters(params) : params, + searchChar, + urlObjectHash = urlObject.hash; + + if (path.isEmbedded(url) && paramsString.length > 0) { + searchChar = urlObject.hashSearch || "?"; + return urlObject.hrefNoHash + (urlObjectHash || "") + searchChar + (searchChar.charAt(searchChar.length - 1) === "?" ? "" : "&") + paramsString; + } + + searchChar = urlObject.search || "?"; + return urlObject.hrefNoSearch + searchChar + (searchChar.charAt(searchChar.length - 1) === "?" ? "" : "&") + paramsString + (urlObjectHash || ""); + }, + + /** + * Add search params to the specified url with hash + * @method addHashSearchParams + * @member ns.util.path + * @param {string|Object} url + * @param {Object|string} params + * @return {string} + */ + addHashSearchParams: function (url, params) { + var urlObject = path.parseUrl(url), + paramsString = (typeof params === "object") ? path.getAsURIParameters(params) : params, + hash = urlObject.hash, + searchChar = hash ? (hash.indexOf("?") < 0 ? hash + "?" : hash + "&") : "#?"; + + return urlObject.hrefNoHash + searchChar + (searchChar.charAt(searchChar.length - 1) === "?" ? "" : "&") + paramsString; + }, + + /** + * Convert absolute Url to data Url + * - for embedded pages strips parameters + * - for the same domain as document base remove domain + * otherwise returns decoded absolute Url + * @method convertUrlToDataUrl + * @member ns.util.path + * @param {string} absUrl + * @param {boolean} dialogHashKey + * @param {Object} documentBase uri structure + * @return {string} + * @static + */ + convertUrlToDataUrl: function (absUrl, dialogHashKey, documentBase) { + var urlObject = path.parseUrl(absUrl); + + if (path.isEmbeddedPage(urlObject, !!dialogHashKey)) { + // Keep hash and search data for embedded page + return path.getFilePath(urlObject.hash + urlObject.hashSearch, dialogHashKey); + } + documentBase = documentBase || path.documentBase; + if (path.isSameDomain(urlObject, documentBase)) { + return urlObject.hrefNoHash.replace(documentBase.domain, ""); + } + + return window.decodeURIComponent(absUrl); + }, + + /** + * Get path from current hash, or from a file path + * @method get + * @member ns.util.path + * @param {string} newPath + * @return {string} + */ + get: function (newPath) { + if (newPath === undefined) { + newPath = this.parseLocation().hash; + } + return this.stripHash(newPath).replace(/[^\/]*\.[^\/*]+$/, ""); + }, + + /** + * Test if a given url (string) is a path + * NOTE might be exceptionally naive + * @method isPath + * @member ns.util.path + * @param {string} url + * @return {boolean} + * @static + */ + isPath: function (url) { + return (/\//).test(url); + }, + + /** + * Return a url path with the window's location protocol/hostname/pathname removed + * @method clean + * @member ns.util.path + * @param {string} url + * @param {Object} documentBase uri structure + * @return {string} + * @static + */ + clean: function (url, documentBase) { + return url.replace(documentBase.domain, ""); + }, + + /** + * Just return the url without an initial # + * @method stripHash + * @member ns.util.path + * @param {string} url + * @return {string} + * @static + */ + stripHash: function (url) { + return url.replace(/^#/, ""); + }, + + /** + * Return the url without an query params + * @method stripQueryParams + * @member ns.util.path + * @param {string} url + * @return {string} + * @static + */ + stripQueryParams: function (url) { + return url.replace(/\?.*$/, ""); + }, + + /** + * Validation proper hash + * @method isHashValid + * @member ns.util.path + * @param {string} hash + * @static + */ + isHashValid: function (hash) { + return (/^#[^#]+$/).test(hash); + }, + + /** + * Check whether a url is referencing the same domain, or an external domain or different + * protocol could be mailto, etc + * @method isExternal + * @member ns.util.path + * @param {string|Object} url + * @param {Object} documentUrl uri object + * @return {boolean} + * @static + */ + isExternal: function (url, documentUrl) { + var urlObject = path.parseUrl(url); + + return urlObject.protocol && urlObject.domain !== documentUrl.domain ? true : false; + }, + + /** + * Check if the url has protocol + * @method hasProtocol + * @member ns.util.path + * @param {string} url + * @return {boolean} + * @static + */ + hasProtocol: function (url) { + return (/^(:?\w+:)/).test(url); + }, + + /** + * Check if the url refers to embedded content + * @method isEmbedded + * @member ns.util.path + * @param {string} url + * @return {boolean} + * @static + */ + isEmbedded: function (url) { + var urlObject = path.parseUrl(url); + + if (urlObject.protocol !== "") { + return (!path.isPath(urlObject.hash) && !!urlObject.hash && (urlObject.hrefNoHash === path.parseLocation().hrefNoHash)); + } + return (/\?.*#|^#/).test(urlObject.href); + }, + + /** + * Get the url as it would look squashed on to the current resolution url + * @method squash + * @member ns.util.path + * @param {string} url + * @param {string} [resolutionUrl=undefined] + * @return {string} + */ + squash: function (url, resolutionUrl) { + var href, + cleanedUrl, + search, + stateIndex, + isPath = this.isPath(url), + uri = this.parseUrl(url), + preservedHash = uri.hash, + uiState = ""; + + // produce a url against which we can resole the provided path + resolutionUrl = resolutionUrl || (path.isPath(url) ? path.getLocation() : path.getDocumentUrl()); + + // If the url is anything but a simple string, remove any preceding hash + // eg #foo/bar -> foo/bar + // #foo -> #foo + cleanedUrl = isPath ? path.stripHash(url) : url; + + // If the url is a full url with a hash check if the parsed hash is a path + // if it is, strip the #, and use it otherwise continue without change + cleanedUrl = path.isPath(uri.hash) ? path.stripHash(uri.hash) : cleanedUrl; + + // Split the UI State keys off the href + stateIndex = cleanedUrl.indexOf(this.uiStateKey); + + // store the ui state keys for use + if (stateIndex > -1) { + uiState = cleanedUrl.slice(stateIndex); + cleanedUrl = cleanedUrl.slice(0, stateIndex); + } + + // make the cleanedUrl absolute relative to the resolution url + href = path.makeUrlAbsolute(cleanedUrl, resolutionUrl); + + // grab the search from the resolved url since parsing from + // the passed url may not yield the correct result + search = this.parseUrl(href).search; + + // @TODO all this crap is terrible, clean it up + if (isPath) { + // reject the hash if it's a path or it's just a dialog key + if (path.isPath(preservedHash) || preservedHash.replace("#", "").indexOf(this.uiStateKey) === 0) { + preservedHash = ""; + } + + // Append the UI State keys where it exists and it's been removed + // from the url + if (uiState && preservedHash.indexOf(this.uiStateKey) === -1) { + preservedHash += uiState; + } + + // make sure that pound is on the front of the hash + if (preservedHash.indexOf("#") === -1 && preservedHash !== "") { + preservedHash = "#" + preservedHash; + } + + // reconstruct each of the pieces with the new search string and hash + href = path.parseUrl(href); + href = href.protocol + "//" + href.host + href.pathname + search + preservedHash; + } else { + href += href.indexOf("#") > -1 ? uiState : "#" + uiState; + } + + return href; + }, + + /** + * Check if the hash is preservable + * @method isPreservableHash + * @member ns.util.path + * @param {string} hash + * @return {boolean} + */ + isPreservableHash: function (hash) { + return hash.replace("#", "").indexOf(this.uiStateKey) === 0; + }, + + /** + * Escape weird characters in the hash if it is to be used as a selector + * @method hashToSelector + * @member ns.util.path + * @param {string} hash + * @return {string} + * @static + */ + hashToSelector: function (hash) { + var hasHash = (hash.substring(0, 1) === "#"); + + if (hasHash) { + hash = hash.substring(1); + } + return (hasHash ? "#" : "") + hash.replace(new RegExp("([!\"#$%&'()*+,./:;<=>?@[\\]^`{|}~])", "g"), "\\$1"); + }, + + /** + * Check if the specified url refers to the first page in the main application document. + * @method isFirstPageUrl + * @member ns.util.path + * @param {string} url + * @param {HTMLElement} firstPageElement first page element + * @param {string} documentBase uri structure + * @param {boolean} documentBaseDiffers + * @param {Object} documentUrl uri structure + * @return {boolean} + * @static + */ + isFirstPageUrl: function (url, firstPageElement, documentBase, documentBaseDiffers, documentUrl) { + var urlStructure, + samePath, + firstPageId, + hash; + + documentBase = documentBase === undefined ? path.documentBase : documentBase; + documentBaseDiffers = documentBaseDiffers === undefined ? path.documentBaseDiffers : documentBaseDiffers; + documentUrl = documentUrl === undefined ? path.documentUrl : documentUrl; + + // We only deal with absolute paths. + urlStructure = path.parseUrl(path.makeUrlAbsolute(url, documentBase)); + + // Does the url have the same path as the document? + samePath = urlStructure.hrefNoHash === documentUrl.hrefNoHash || (documentBaseDiffers && urlStructure.hrefNoHash === documentBase.hrefNoHash); + + // Get the id of the first page element if it has one. + firstPageId = firstPageElement && firstPageElement.id || false; + hash = urlStructure.hash; + + // The url refers to the first page if the path matches the document and + // it either has no hash value, or the hash is exactly equal to the id of the + // first page element. + return samePath && (!hash || hash === "#" || (firstPageId && hash.replace(/^#/, "") === firstPageId)); + }, + + /** + * Some embedded browsers, like the web view in Phone Gap, allow cross-domain XHR + * requests if the document doing the request was loaded via the file:// protocol. + * This is usually to allow the application to "phone home" and fetch app specific + * data. We normally let the browser handle external/cross-domain urls, but if the + * allowCrossDomainPages option is true, we will allow cross-domain http/https + * requests to go through our page loading logic. + * @method isPermittedCrossDomainRequest + * @member ns.util.path + * @param {Object} docUrl + * @param {string} reqUrl + * @return {boolean} + * @static + */ + isPermittedCrossDomainRequest: function (docUrl, reqUrl) { + return ns.getConfig("allowCrossDomainPages", false) && + docUrl.protocol === "file:" && + reqUrl.search(/^https?:/) !== -1; + }, + + /** + * Convert a object data to URI parameters + * @method getAsURIParameters + * @member ns.util.path + * @param {Object} data + * @return {string} + * @static + */ + getAsURIParameters: function (data) { + var url = "", + key; + + for (key in data) { + if (data.hasOwnProperty(key)) { + url += encodeURIComponent(key) + "=" + encodeURIComponent(data[key]) + "&"; + } + } + return url.substring(0, url.length - 1); + }, + + /** + * Document Url + * @member ns.util.path + * @property {string|null} documentUrl + */ + documentUrl: null, + + /** + * The document base differs + * @member ns.util.path + * @property {boolean} documentBaseDiffers + */ + documentBaseDiffers: false, + + /** + * Set location hash to path + * @method set + * @member ns.util.path + * @param {string} path + * @static + */ + set: function (path) { + location.hash = path; + }, + + /** + * Return the substring of a file path before the sub-page key, + * for making a server request + * @method getFilePath + * @member ns.util.path + * @param {string} path + * @param {string} dialogHashKey + * @return {string} + * @static + */ + getFilePath: function (path, dialogHashKey) { + var splitKey = "&" + ns.getConfig("subPageUrlKey", ""); + + return path && path.split(splitKey)[0].split(dialogHashKey)[0]; + }, + + /** + * Remove the preceding hash, any query params, and dialog notations + * @method cleanHash + * @member ns.util.path + * @param {string} hash + * @param {string} dialogHashKey + * @return {string} + * @static + */ + cleanHash: function (hash, dialogHashKey) { + return path.stripHash(hash.replace(/\?.*$/, "").replace(dialogHashKey, "")); + }, + + /** + * Check if url refers to the embedded page + * @method isEmbeddedPage + * @member ns.util.path + * @param {string} url + * @param {boolean} allowEmbeddedOnlyBaseDoc + * @return {boolean} + * @static + */ + isEmbeddedPage: function (url, allowEmbeddedOnlyBaseDoc) { + var urlObject = path.parseUrl(url); + + //if the path is absolute, then we need to compare the url against + //both the documentUrl and the documentBase. The main reason for this + //is that links embedded within external documents will refer to the + //application document, whereas links embedded within the application + //document will be resolved against the document base. + if (urlObject.protocol !== "") { + return (urlObject.hash && + (allowEmbeddedOnlyBaseDoc ? + urlObject.hrefNoHash === path.documentUrl.hrefNoHash : + urlObject.hrefNoHash === path.parseLocation().hrefNoHash)); + } + return (/^#/).test(urlObject.href); + } + }; + + path.documentUrl = path.parseLocation(); + + base = document.querySelector("base"); + + /** + * The document base URL for the purposes of resolving relative URLs, + * and the name of the default browsing context for the purposes of + * following hyperlinks + * @member ns.util.path + * @property {Object} documentBase uri structure + * @static + */ + path.documentBase = base ? path.parseUrl(path.makeUrlAbsolute(base.getAttribute("href"), + path.documentUrl.href)) : path.documentUrl; + + path.documentBaseDiffers = (path.documentUrl.hrefNoHash !== path.documentBase.hrefNoHash); + + /** + * Get document base + * @method getDocumentBase + * @member ns.util.path + * @param {boolean} [asParsedObject=false] + * @return {string|Object} + * @static + */ + path.getDocumentBase = function (asParsedObject) { + return asParsedObject ? utilsObject.copy(path.documentBase) : path.documentBase.href; + }; + + /** + * Find the closest page and extract out its url + * @method getClosestBaseUrl + * @member ns.util.path + * @param {HTMLElement} element + * @param {string} selector + * @return {string} + * @static + */ + path.getClosestBaseUrl = function (element, selector) { + // Find the closest page and extract out its url. + var url = utilsDOM.getNSData(utilsSelectors.getClosestBySelector(element, selector), "url"), + baseUrl = path.documentBase.hrefNoHash; + + if (!ns.getConfig("dynamicBaseEnabled", true) || !url || !path.isPath(url)) { + url = baseUrl; + } + + return path.makeUrlAbsolute(url, baseUrl); + }; + + ns.util.path = path; + }(window, window.document, ns)); + +/** + * + */ +(function (window) { + "use strict"; + var isarray = Array.isArray; + + pathToRegexp.parse = parse + pathToRegexp.compile = compile + pathToRegexp.tokensToFunction = tokensToFunction + pathToRegexp.tokensToRegExp = tokensToRegExp + +/** + * Start original file + * Licence MIT + * https://github.com/pillarjs/path-to-regexp + */ +/** + * The main path matching regexp utility. + * + * @type {RegExp} + */ +var PATH_REGEXP = new RegExp([ + // Match escaped characters that would otherwise appear in future matches. + // This allows the user to escape special characters that won't transform. + '(\\\\.)', + // Match Express-style parameters and un-named parameters with a prefix + // and optional suffixes. Matches appear as: + // + // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?"] + // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined] + '([\\/.])?(?:\\:(\\w+)(?:\\(((?:\\\\.|[^)])*)\\))?|\\(((?:\\\\.|[^)])*)\\))([+*?])?' +].join('|'), 'g') + +/** + * Parse a string for the raw tokens. + * + * @param {String} str + * @return {Array} + */ +function parse (str) { + var tokens = [] + var key = 0 + var index = 0 + var path = '' + var res + + while ((res = PATH_REGEXP.exec(str)) != null) { + var m = res[0] + var escaped = res[1] + var offset = res.index + path += str.slice(index, offset) + index = offset + m.length + + // Ignore already escaped sequences. + if (escaped) { + path += escaped[1] + continue + } + + // Push the current path onto the tokens. + if (path) { + tokens.push(path) + path = '' + } + + var prefix = res[2] + var name = res[3] + var capture = res[4] + var group = res[5] + var suffix = res[6] + + var repeat = suffix === '+' || suffix === '*' + var optional = suffix === '?' || suffix === '*' + var delimiter = prefix || '/' + + tokens.push({ + name: name || key++, + prefix: prefix || '', + delimiter: delimiter, + optional: optional, + repeat: repeat, + pattern: escapeGroup(capture || group || '[^' + delimiter + ']+?') + }) + } + + // Match any characters still remaining. + if (index < str.length) { + path += str.substr(index) + } + + // If the path exists, push it onto the end. + if (path) { + tokens.push(path) + } + + return tokens +} + +/** + * Compile a string to a template function for the path. + * + * @param {String} str + * @return {Function} + */ +function compile (str) { + return tokensToFunction(parse(str)) +} + +/** + * Expose a method for transforming tokens into the path function. + */ +function tokensToFunction (tokens) { + // Compile all the tokens into regexps. + var matches = new Array(tokens.length) + + // Compile all the patterns before compilation. + for (var i = 0; i < tokens.length; i++) { + if (typeof tokens[i] === 'object') { + matches[i] = new RegExp('^' + tokens[i].pattern + '$') + } + } + + return function (obj) { + var path = '' + + obj = obj || {} + + for (var i = 0; i < tokens.length; i++) { + var key = tokens[i] + + if (typeof key === 'string') { + path += key + + continue + } + + var value = obj[key.name] + + if (value == null) { + if (key.optional) { + continue + } else { + throw new TypeError('Expected "' + key.name + '" to be defined') + } + } + + if (isarray(value)) { + if (!key.repeat) { + throw new TypeError('Expected "' + key.name + '" to not repeat') + } + + if (value.length === 0) { + if (key.optional) { + continue + } else { + throw new TypeError('Expected "' + key.name + '" to not be empty') + } + } + + for (var j = 0; j < value.length; j++) { + if (!matches[i].test(value[j])) { + throw new TypeError('Expected all "' + key.name + '" to match "' + key.pattern + '"') + } + + path += (j === 0 ? key.prefix : key.delimiter) + encodeURIComponent(value[j]) + } + + continue + } + + if (!matches[i].test(value)) { + throw new TypeError('Expected "' + key.name + '" to match "' + key.pattern + '"') + } + + path += key.prefix + encodeURIComponent(value) + } + + return path + } +} + +/** + * Escape a regular expression string. + * + * @param {String} str + * @return {String} + */ +function escapeString (str) { + return str.replace(/([.+*?=^!:${}()[\]|\/])/g, '\\$1') +} + +/** + * Escape the capturing group by escaping special characters and meaning. + * + * @param {String} group + * @return {String} + */ +function escapeGroup (group) { + return group.replace(/([=!:$\/()])/g, '\\$1') +} + +/** + * Attach the keys as a property of the regexp. + * + * @param {RegExp} re + * @param {Array} keys + * @return {RegExp} + */ +function attachKeys (re, keys) { + re.keys = keys + return re +} + +/** + * Get the flags for a regexp from the options. + * + * @param {Object} options + * @return {String} + */ +function flags (options) { + return options.sensitive ? '' : 'i' +} + +/** + * Pull out keys from a regexp. + * + * @param {RegExp} path + * @param {Array} keys + * @return {RegExp} + */ +function regexpToRegexp (path, keys) { + // Use a negative lookahead to match only capturing groups. + var groups = path.source.match(/\((?!\?)/g) + + if (groups) { + for (var i = 0; i < groups.length; i++) { + keys.push({ + name: i, + prefix: null, + delimiter: null, + optional: false, + repeat: false, + pattern: null + }) + } + } + + return attachKeys(path, keys) +} + +/** + * Transform an array into a regexp. + * + * @param {Array} path + * @param {Array} keys + * @param {Object} options + * @return {RegExp} + */ +function arrayToRegexp (path, keys, options) { + var parts = [] + + for (var i = 0; i < path.length; i++) { + parts.push(pathToRegexp(path[i], keys, options).source) + } + + var regexp = new RegExp('(?:' + parts.join('|') + ')', flags(options)) + + return attachKeys(regexp, keys) +} + +/** + * Create a path regexp from string input. + * + * @param {String} path + * @param {Array} keys + * @param {Object} options + * @return {RegExp} + */ +function stringToRegexp (path, keys, options) { + var tokens = parse(path) + var re = tokensToRegExp(tokens, options) + + // Attach keys back to the regexp. + for (var i = 0; i < tokens.length; i++) { + if (typeof tokens[i] !== 'string') { + keys.push(tokens[i]) + } + } + + return attachKeys(re, keys) +} + +/** + * Expose a function for taking tokens and returning a RegExp. + * + * @param {Array} tokens + * @param {Array} keys + * @param {Object} options + * @return {RegExp} + */ +function tokensToRegExp (tokens, options) { + options = options || {} + + var strict = options.strict + var end = options.end !== false + var route = '' + var lastToken = tokens[tokens.length - 1] + var endsWithSlash = typeof lastToken === 'string' && /\/$/.test(lastToken) + + // Iterate over the tokens and create our regexp string. + for (var i = 0; i < tokens.length; i++) { + var token = tokens[i] + + if (typeof token === 'string') { + route += escapeString(token) + } else { + var prefix = escapeString(token.prefix) + var capture = token.pattern + + if (token.repeat) { + capture += '(?:' + prefix + capture + ')*' + } + + if (token.optional) { + if (prefix) { + capture = '(?:' + prefix + '(' + capture + '))?' + } else { + capture = '(' + capture + ')?' + } + } else { + capture = prefix + '(' + capture + ')' + } + + route += capture + } + } + + // In non-strict mode we allow a slash at the end of match. If the path to + // match already ends with a slash, we remove it for consistency. The slash + // is valid at the end of a path match, not in the middle. This is important + // in non-ending mode, where "/test/" shouldn't match "/test//route". + if (!strict) { + route = (endsWithSlash ? route.slice(0, -2) : route) + '(?:\\/(?=$))?' + } + + if (end) { + route += '$' + } else { + // In non-ending mode, we need the capturing groups to match as much as + // possible by using a positive lookahead to the end or next path segment. + route += strict && endsWithSlash ? '' : '(?=\\/|$)' + } + + return new RegExp('^' + route, flags(options)) +} + +/** + * Normalize the given path string, returning a regular expression. + * + * An empty array can be passed in for the keys, which will hold the + * placeholder key descriptions. For example, using `/user/:id`, `keys` will + * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`. + * + * @param {(String|RegExp|Array)} path + * @param {Array} [keys] + * @param {Object} [ + * + * options] + * @return {RegExp} + */ +function pathToRegexp (path, keys, options) { + keys = keys || [] + + if (!isarray(keys)) { + options = keys + keys = [] + } else if (!options) { + options = {} + } + + if (path instanceof RegExp) { + return regexpToRegexp(path, keys, options) + } + + if (isarray(path)) { + return arrayToRegexp(path, keys, options) + } + + return stringToRegexp(path, keys, options) +} + +/** + * End original file + */ + window.pathToRegexp = pathToRegexp; + }(window)); +/*global define, ns */ +/* + * Copyright (c) 2010 - 2014 Samsung Electronics Co., Ltd. + * License : MIT License V2 + */ +/** + * #Path to Regexp Utility + * Convert string to regexp and can match path string to one defined regex path + * + * Syntax of paths is the same as in Express for nodejs + * + * Library based on https://github.com/pillarjs/path-to-regexp + * @class ns.util.pathToRegexp + * @author Maciej Urbanski + * + */ +(function (window) { + "use strict"; + + ns.util.pathToRegexp = window.pathToRegexp; + + }(window)); + +/*global define, ns */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * #Router + * Namespace for routers + * @author Maciej Urbanski + * @author Krzysztof Antoszek + * @class ns.router + */ +(function () { + "use strict"; + ns.router = ns.router || {}; + }()); + +/*global define, ns */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * #Route Namespace + * Object contains rules for router. + * + * @class ns.router.route + */ +/* + * @author Maciej Urbanski + */ +(function () { + "use strict"; + ns.router.route = ns.router.route || {}; + }()); + +/*global window, ns, define, ns */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +/** + * #History + * Object controls history changes. + * + * @class ns.history + * @author Maciej Urbanski + */ +(function (window) { + "use strict"; + var historyVolatileMode, + object = ns.util.object, + historyUid = 0, + historyActiveIndex = 0, + windowHistory = window.history, + history = { + /** + * Property contains active state in history. + * @property {Object} activeState + * @static + * @member ns.history + */ + activeState: null, + + /** + * This method replaces or pushes state to history. + * @method replace + * @param {Object} state The state object + * @param {string} stateTitle The title of state + * @param {string} url The new history entry's URL + * @static + * @member ns.history + */ + replace: function (state, stateTitle, url) { + var newState = object.merge({}, state, { + uid: historyVolatileMode ? historyActiveIndex : ++historyUid, + stateUrl: url, + stateTitle: stateTitle + }); + + windowHistory[historyVolatileMode ? "replaceState" : "pushState"](newState, stateTitle, url); + history.setActive(newState); + }, + + /** + * This method moves backward through history. + * @method back + * @static + * @member ns.history + */ + back: function () { + windowHistory.back(); + }, + + /** + * This method sets active state. + * @method setActive + * @param {Object} state Activated state + * @static + * @member ns.history + */ + setActive: function (state) { + if (state) { + history.activeState = state; + historyActiveIndex = state.uid; + + if (state.volatileRecord) { + history.enableVolatileMode(); + return; + } + } + + history.disableVolatileMode(); + }, + + /** + * This method returns "back" if state is in history or "forward" if it is new state. + * @method getDirection + * @param {Object} state Checked state + * @return {"back"|"forward"} + * @static + * @member ns.history + */ + getDirection: function (state) { + if (state) { + return state.uid <= historyActiveIndex ? "back" : "forward"; + } + return "back"; + }, + + /** + * This method sets volatile mode to true. + * @method enableVolatileMode + * @static + * @member ns.history + */ + enableVolatileMode: function () { + historyVolatileMode = true; + }, + + /** + * This method sets volatile mode to false. + * @method disableVolatileMode + * @static + * @member ns.history + */ + disableVolatileMode: function () { + historyVolatileMode = false; + } + }; + + ns.history = history; + }(window)); + +/*global CustomEvent, define, window, ns */ +/*jslint plusplus: true, nomen: true, bitwise: true */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * #Virtual Mouse Events + * Reimplementation of jQuery Mobile virtual mouse events. + * + * ##Purpose + * It will let for users to register callbacks to the standard events like bellow, + * without knowing if device support touch or mouse events + * @class ns.event.vmouse + */ +/** + * Triggered after mouse-down or touch-started. + * @event vmousedown + * @member ns.event.vmouse + */ +/** + * Triggered when mouse-click or touch-end when touch-move didn't occur + * @event vclick + * @member ns.event.vmouse + */ +/** + * Triggered when mouse-up or touch-end + * @event vmouseup + * @member ns.event.vmouse + */ +/** + * Triggered when mouse-move or touch-move + * @event vmousemove + * @member ns.event.vmouse + */ +/** + * Triggered when mouse-over or touch-start if went over coordinates + * @event vmouseover + * @member ns.event.vmouse + */ +/** + * Triggered when mouse-out or touch-end + * @event vmouseout + * @member ns.event.vmouse + */ +/** + * Triggered when mouse-cancel or touch-cancel and when scroll occur during touchmove + * @event vmousecancel + * @member ns.event.vmouse + */ +(function (window, document, ns) { + "use strict"; + /** + * Object with default options + * @property {Object} vmouse + * @member ns.event.vmouse + * @static + * @private + **/ + var vmouse, + /** + * @property {Object} eventProps Contains the properties which are copied from the original + * event to custom v-events + * @member ns.event.vmouse + * @static + * @private + **/ + eventProps, + /** + * Indicates if the browser support touch events + * @property {boolean} touchSupport + * @member ns.event.vmouse + * @static + **/ + touchSupport = window.hasOwnProperty("ontouchstart"), + /** + * @property {boolean} didScroll The flag tell us if the scroll event was triggered + * @member ns.event.vmouse + * @static + * @private + **/ + didScroll, + /** @property {HTMLElement} lastOver holds reference to last element that touch was over + * @member ns.event.vmouse + * @private + */ + lastOver = null, + /** + * @property {number} [startX=0] Initial data for touchstart event + * @member ns.event.vmouse + * @static + * @private + **/ + startX = 0, + /** + * @property {number} [startY=0] Initial data for touchstart event + * @member ns.event.vmouse + * @private + * @static + **/ + startY = 0, + touchEventProps = ["clientX", "clientY", "pageX", "pageY", "screenX", "screenY"], + KEY_CODES = { + enter: 13 + }; + + /** + * Extends objects with other objects + * @method copyProps + * @param {Object} from Sets the original event + * @param {Object} to Sets the new event + * @param {Object} properties Sets the special properties for position + * @param {Object} propertiesNames Describe parameters which will be copied from Original to + * event + * @private + * @static + * @member ns.event.vmouse + */ + function copyProps(from, to, properties, propertiesNames) { + var i, + length, + descriptor, + property; + + for (i = 0, length = propertiesNames.length; i < length; ++i) { + property = propertiesNames[i]; + if (isNaN(properties[property]) === false || isNaN(from[property]) === false) { + descriptor = Object.getOwnPropertyDescriptor(to, property); + if (property !== "detail" && (!descriptor || descriptor.writable)) { + to[property] = properties[property] || from[property]; + } + } + } + } + + /** + * Create custom event + * @method createEvent + * @param {string} newType gives a name for the new Type of event + * @param {Event} original Event which trigger the new event + * @param {Object} properties Sets the special properties for position + * @return {Event} + * @private + * @static + * @member ns.event.vmouse + */ + function createEvent(newType, original, properties) { + var evt = new CustomEvent(newType, { + "bubbles": original.bubbles, + "cancelable": original.cancelable, + "detail": original.detail + }), + originalType = original.type, + changeTouches, + touch, + j = 0, + len, + prop; + + copyProps(original, evt, properties, eventProps); + evt._originalEvent = original; + + if (originalType.indexOf("touch") !== -1) { + originalType = original.touches; + changeTouches = original.changedTouches; + + if (originalType && originalType.length) { + touch = originalType[0]; + } else { + touch = (changeTouches && changeTouches.length) ? changeTouches[0] : null; + } + + if (touch) { + for (len = touchEventProps.length; j < len; j++) { + prop = touchEventProps[j]; + evt[prop] = touch[prop]; + } + } + } + + return evt; + } + + /** + * Dispatch Events + * @method fireEvent + * @param {string} eventName event name + * @param {Event} evt original event + * @param {Object} [properties] Sets the special properties for position + * @return {boolean} + * @private + * @static + * @member ns.event.vmouse + */ + function fireEvent(eventName, evt, properties) { + return evt.target.dispatchEvent(createEvent(eventName, evt, properties || {})); + } + + eventProps = [ + "currentTarget", + "detail", + "button", + "buttons", + "clientX", + "clientY", + "offsetX", + "offsetY", + "pageX", + "pageY", + "screenX", + "screenY", + "toElement", + "which" + ]; + + vmouse = { + /** + * Sets the distance of pixels after which the scroll event will be successful + * @property {number} [eventDistanceThreshold=10] + * @member ns.event.vmouse + * @static + */ + eventDistanceThreshold: 10, + + touchSupport: touchSupport + }; + + /** + * Handle click down + * @method handleDown + * @param {Event} evt + * @private + * @static + * @member ns.event.vmouse + */ + function handleDown(evt) { + fireEvent("vmousedown", evt); + } + + /** + * Prepare position of event for keyboard events. + * @method preparePositionForClick + * @param {Event} event + * @return {?Object} options + * @private + * @static + * @member ns.event.vmouse + */ + function preparePositionForClick(event) { + var x = event.clientX, + y = event.clientY; + // event comes from keyboard + + if (!x && !y) { + return preparePositionForEvent(event); + } + } + + /** + * Handle click + * @method handleClick + * @param {Event} evt + * @private + * @static + * @member ns.event.vmouse + */ + function handleClick(evt) { + fireEvent("vclick", evt, preparePositionForClick(evt)); + } + + /** + * Handle click up + * @method handleUp + * @param {Event} evt + * @private + * @static + * @member ns.event.vmouse + */ + function handleUp(evt) { + fireEvent("vmouseup", evt); + } + + /** + * Handle click move + * @method handleMove + * @param {Event} evt + * @private + * @static + * @member ns.event.vmouse + */ + function handleMove(evt) { + fireEvent("vmousemove", evt); + } + + /** + * Handle click over + * @method handleOver + * @param {Event} evt + * @private + * @static + * @member ns.event.vmouse + */ + function handleOver(evt) { + fireEvent("vmouseover", evt); + } + + /** + * Handle click out + * @method handleOut + * @param {Event} evt + * @private + * @static + * @member ns.event.vmouse + */ + function handleOut(evt) { + fireEvent("vmouseout", evt); + } + + /** + * Handle touch start + * @method handleTouchStart + * @param {Event} evt + * @private + * @static + * @member ns.event.vmouse + */ + function handleTouchStart(evt) { + var touches = evt.touches, + firstTouch; + + //if touches are registered and we have only one touch + if (touches && touches.length === 1) { + didScroll = false; + firstTouch = touches[0]; + startX = firstTouch.pageX; + startY = firstTouch.pageY; + + // Check if we have touched something on our page + // @TODO refactor for multi touch + /* + over = document.elementFromPoint(startX, startY); + if (over) { + lastOver = over; + fireEvent("vmouseover", evt); + } + */ + fireEvent("vmousedown", evt); + } + + } + + /** + * Handle touch end + * @method handleTouchEnd + * @param {Event} evt + * @private + * @static + * @member ns.event.vmouse + */ + function handleTouchEnd(evt) { + var touches = evt.touches; + + if (touches && touches.length === 0) { + fireEvent("vmouseup", evt); + fireEvent("vmouseout", evt); + // Reset flag for last over element + lastOver = null; + } + } + + /** + * Handle touch move + * @method handleTouchMove + * @param {Event} evt + * @private + * @static + * @member ns.event.vmouse + */ + function handleTouchMove(evt) { + var over, + firstTouch = evt.touches && evt.touches[0], + didCancel = didScroll, + //sets the threshold, based on which we consider if it was the touch-move event + moveThreshold = vmouse.eventDistanceThreshold; + + /** + * Ignore the touch which has identifier other than 0. + * Only first touch has control others are ignored. + * Patch for webkit behavior where touchmove event + * is triggered between touchend events + * if there is multi touch. + */ + + if ((firstTouch === undefined) || firstTouch.identifier > 0) { + //evt.preventDefault(); // cant preventDefault passive events!!! + evt.stopPropagation(); + return; + } + + didScroll = didScroll || + //check in both axes X,Y if the touch-move event occur + (Math.abs(firstTouch.pageX - startX) > moveThreshold || + Math.abs(firstTouch.pageY - startY) > moveThreshold); + + // detect over event + // for compatibility with mouseover because "touchenter" fires only once + // @TODO Handle many touches + over = document.elementFromPoint(firstTouch.pageX, firstTouch.pageY); + if (over && lastOver !== over) { + lastOver = over; + fireEvent("vmouseover", evt); + } + + //if didScroll occur and wasn't canceled then trigger touchend otherwise just touchmove + if (didScroll && !didCancel) { + fireEvent("vmousecancel", evt); + lastOver = null; + } + fireEvent("vmousemove", evt); + } + + /** + * Handle Scroll + * @method handleScroll + * @param {Event} evt + * @private + * @static + * @member ns.event.vmouse + */ + function handleScroll(evt) { + if (!didScroll) { + fireEvent("vmousecancel", evt); + } + didScroll = true; + } + + /** + * Handle touch cancel + * @method handleTouchCancel + * @param {Event} evt + * @private + * @static + * @member ns.event.vmouse + */ + function handleTouchCancel(evt) { + + fireEvent("vmousecancel", evt); + lastOver = null; + } + + /** + * Prepare position of event for keyboard events. + * @method preparePositionForEvent + * @param {Event} event + * @return {Object} properties + * @private + * @static + * @member ns.event.vmouse + */ + function preparePositionForEvent(event) { + var targetRect = event.target && event.target.getBoundingClientRect(), + properties = {}; + + if (targetRect) { + properties = { + "clientX": targetRect.left + targetRect.width / 2, + "clientY": targetRect.top + targetRect.height / 2, + "which": 1 + }; + } + return properties; + } + + /** + * Handle key up + * @method handleKeyUp + * @param {Event} event + * @private + * @static + * @member ns.event.vmouse + */ + function handleKeyUp(event) { + var properties; + + if (event.keyCode === KEY_CODES.enter) { + properties = preparePositionForEvent(event); + fireEvent("vmouseup", event, properties); + fireEvent("vclick", event, properties); + } + } + + /** + * Handle key down + * @method handleKeyDown + * @param {Event} event + * @private + * @static + * @member ns.event.vmouse + */ + function handleKeyDown(event) { + if (event.keyCode === KEY_CODES.enter) { + fireEvent("vmousedown", event, preparePositionForEvent(event)); + } + } + + /** + * Binds events common to mouse and touch to support virtual mouse. + * @method bindCommonEvents + * @static + * @member ns.event.vmouse + */ + vmouse.bindCommonEvents = function () { + document.addEventListener("keyup", handleKeyUp, true); + document.addEventListener("keydown", handleKeyDown, true); + document.addEventListener("scroll", handleScroll, true); + document.addEventListener("click", handleClick, true); + }; + + // @TODO delete touchSupport flag and attach touch and mouse listeners, + // @TODO check if v-events are not duplicated if so then called only once + + /** + * Binds touch events to support virtual mouse. + * @method bindTouch + * @static + * @member ns.event.vmouse + */ + vmouse.bindTouch = function () { + document.addEventListener("touchstart", handleTouchStart, true); + document.addEventListener("touchend", handleTouchEnd, true); + document.addEventListener("touchmove", handleTouchMove, true); + document.addEventListener("touchcancel", handleTouchCancel, true); + }; + + /** + * Binds mouse events to support virtual mouse. + * @method bindMouse + * @static + * @member ns.event.vmouse + */ + vmouse.bindMouse = function () { + document.addEventListener("mousedown", handleDown, true); + document.addEventListener("mouseup", handleUp, true); + document.addEventListener("mousemove", handleMove, true); + document.addEventListener("mouseover", handleOver, true); + document.addEventListener("mouseout", handleOut, true); + }; + + /** + * Unbinds touch events to support virtual mouse. + * @method unbindTouch + * @static + * @member ns.event.vmouse + */ + vmouse.unbindTouch = function () { + document.removeEventListener("touchstart", handleTouchStart, true); + document.removeEventListener("touchend", handleTouchEnd, true); + document.removeEventListener("touchmove", handleTouchMove, true); + + document.removeEventListener("touchcancel", handleTouchCancel, true); + + document.removeEventListener("click", handleClick, true); + }; + + /** + * Unbinds mouse events to support virtual mouse. + * @method unbindMouse + * @static + * @member ns.event.vmouse + */ + vmouse.unbindMouse = function () { + document.removeEventListener("mousedown", handleDown, true); + + document.removeEventListener("mouseup", handleUp, true); + document.removeEventListener("mousemove", handleMove, true); + document.removeEventListener("mouseover", handleOver, true); + document.removeEventListener("mouseout", handleOut, true); + + document.removeEventListener("keyup", handleKeyUp, true); + document.removeEventListener("keydown", handleKeyDown, true); + document.removeEventListener("scroll", handleScroll, true); + document.removeEventListener("click", handleClick, true); + }; + + ns.event.vmouse = vmouse; + + if (touchSupport) { + vmouse.bindTouch(); + } else { + vmouse.bindMouse(); + } + vmouse.bindCommonEvents(); + + }(window, window.document, ns)); + +/*global window, define, ns */ +/*jslint browser: true, nomen: true */ +/** + * # History manager + * + * Control events connected with history change and trigger events to controller + * or router. + * + * @class ns.history.manager + * @since 2.4 + * @author Krzysztof Antoszek + * @author Maciej Urbanski + * @author Tomasz Lukawski + */ +/** + * Event historystatechange + * @event historystatechange + * @class ns.history.manager + */ +/** + * Event historyhashchange + * @event historyhashchange + * @class ns.history.manager + */ +/** + * Event historyenabled + * @event historyenabled + * @class ns.history.manager + */ +/** + * Event historydisabled + * @event historydisabled + * @class ns.history.manager + */ +(function (window, document) { + "use strict"; + var manager = Object.create(null), // we don't need the Object proto + WINDOW_EVENT_POPSTATE = "popstate", + WINDOW_EVENT_HASHCHANGE = "hashchange", + DOC_EVENT_VCLICK = "vclick", + LINK_SELECTOR = "a,tau-button", + util = ns.util, + history = ns.history, + eventUtils = ns.event, + selectorUtils = util.selectors, + objectUtils = util.object, + pathUtils = util.path, + DOM = util.DOM, + EVENT_STATECHANGE = "historystatechange", + EVENT_HASHCHANGE = "historyhashchange", + EVENT_ENABLED = "historyenabled", + EVENT_DISABLED = "historydisabled", + /** + * Engine event types + * @property {Object} events + * @property {string} events.STATECHANGE="historystatechange" event name on history manager change state + * @property {string} events.HASHCHANGE="historyhashchange" event name on history manager change hash + * @property {string} events.ENABLED="historyenabled" event name on enable history manager + * @property {string} events.DISABLED="historydisabled" event name on disable history manager + * @static + * @readonly + * @member ns.history.manager + */ + events = { + STATECHANGE: EVENT_STATECHANGE, + HASHCHANGE: EVENT_HASHCHANGE, + ENABLED: EVENT_ENABLED, + DISABLED: EVENT_DISABLED + }; + + manager.events = events; + + /** + * Trigger event "historystatechange" on document + * @param {Object} options + * @return {boolean} + */ + function triggerStateChange(options) { + return eventUtils.trigger(document, EVENT_STATECHANGE, options, true, true); + } + + /** + * Callback for link click + * @param {Event} event + * @return {boolean} + */ + function onLinkAction(event) { + var target = event.target, + link = selectorUtils.getClosestBySelector(target, LINK_SELECTOR), + href, + useDefaultUrlHandling, + options, // this should be empty object but some utils that work on it + rel; // require hasOwnProperty :( + + if (link && event.which === 1) { + href = link.getAttribute("href"); + rel = link.getAttribute("rel"); + useDefaultUrlHandling = rel === "external" || link.hasAttribute("target"); + if (!useDefaultUrlHandling) { + options = DOM.getData(link); + options.event = event; + if (rel && !options.rel) { + options.rel = rel; + } else { + rel = options.rel; + } + if (href && !options.href) { + options.href = href; + } + if (rel === "popup" && link && !options.link) { + options.link = link; + } + history.disableVolatileMode(); + if (!triggerStateChange(options)) { + // mark as handled + // but not on back + if (!rel || (rel !== "back")) { + eventUtils.preventDefault(event); + return false; + } + } + } + } + return true; + } + + + /** + * Callback on popstate event. + * @param {Event} event + */ + function onPopState(event) { + var state = event.state, + lastState = history.activeState, + options = {}, + reverse, + resultOfTigger = true, + skipTriggerStateChange = false; + + if (manager.locked) { + history.disableVolatileMode(); + if (lastState) { + history.replace(lastState, lastState.stateTitle, lastState.stateUrl); + } + } else if (state) { + reverse = history.getDirection(state) === "back"; + options = objectUtils.merge(options, state, { + reverse: reverse, + transition: reverse ? ((lastState && lastState.transition) || "none") : state.transition, + fromHashChange: true + }); + + if (lastState) { + resultOfTigger = eventUtils.trigger(document, EVENT_HASHCHANGE, objectUtils.merge(options, + {url: pathUtils.getLocation(), stateUrl: lastState.stateUrl}), true, true); + + + // if EVENT HASHCHANGE has been triggered successfuly then skip trigger HistoryStateChange + skipTriggerStateChange = resultOfTigger; + } + + state.url = pathUtils.getLocation(); + history.setActive(state); + + if (!skipTriggerStateChange) { + options.event = event; + triggerStateChange(options); + } + } + } + + /** + * Callback on "hashchange" event + * @param {Event} event + */ + function onHashChange(event) { + var newURL = event.newURL; + + if (newURL && history.activeState.url !== newURL) { + triggerStateChange({href: newURL, fromHashChange: true, event: event}); + } + } + + /** + * Inform that manager is enabled or not. + * @property {boolean} [enabled=true] + * @static + * @since 2.4 + * @member ns.history.manager + */ + manager.enabled = true; + /** + * Informs that manager is enabled or not. + * + * If manager is locked then not trigger events historystatechange. + * @property {boolean} [locked=false] + * @static + * @since 2.4 + * @member ns.history.manager + */ + manager.locked = false; + + /** + * Locks history manager. + * + * Sets locked property to true. + * + * @example + * tau.history.manager.lock(); + * + * @method lock + * @static + * @since 2.4 + * @member ns.history.manager + */ + manager.lock = function () { + this.locked = true; + }; + + /** + * Unlocks history manager. + * + * Sets locked property to false. + * + * @example + * tau.history.manager.unlock(); + * + * @method unlock + * @static + * @since 2.4 + * @member ns.history.manager + */ + manager.unlock = function () { + this.locked = false; + }; + + /** + * Enables history manager. + * + * This method adds all event listeners connected with history manager. + * + * Event listeners: + * + * - popstate on window + * - hashchange on window + * - vclick on document + * + * After set event listeners method sets property enabled to true. + * + * @example + * tau.history.manager.enable(); + * // add event's listeners + * // after click on link or hash change history manager will handle events + * + * @method enable + * @static + * @since 2.4 + * @member ns.history.manager + */ + manager.enable = function () { + document.addEventListener(DOC_EVENT_VCLICK, onLinkAction, false); + window.addEventListener(WINDOW_EVENT_POPSTATE, onPopState, false); + window.addEventListener(WINDOW_EVENT_HASHCHANGE, onHashChange, false); + history.enableVolatileMode(); + this.enabled = true; + eventUtils.trigger(document, EVENT_ENABLED, this); + }; + + /** + * Disables history manager. + * + * This method removes all event listeners connected with history manager. + * + * After set event listeners method sets property enabled to true. + * + * @example + * tau.history.manager.disable(); + * // remove event's listeners + * // after click on link or hash change history manager will not handle events + * + * @method disable + * @static + * @since 2.4 + * @member ns.history.manager + */ + manager.disable = function () { + document.removeEventListener(DOC_EVENT_VCLICK, onLinkAction, false); + window.removeEventListener(WINDOW_EVENT_POPSTATE, onPopState, false); + window.removeEventListener(WINDOW_EVENT_HASHCHANGE, onHashChange, false); + history.disableVolatileMode(); + this.enabled = false; + eventUtils.trigger(document, EVENT_DISABLED, this); + }; + + ns.history.manager = manager; + }(window, window.document)); + +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/*global define, ns */ +/** + * #String Utility + * Utility helps work with strings. + * @class ns.util.string + */ +(function () { + "use strict"; + var DASH_TO_UPPER_CASE_REGEXP = /-([a-z])/gi, + UPPER_TO_DASH_CASE_REGEXP = /([A-Z])/g, + arrayUtil = ns.util.array; + + /** + * Callback method for regexp used in dashesToCamelCase method + * @method toUpperCaseFn + * @param {string} match + * @param {string} value + * @return {string} + * @member ns.util.string + * @static + * @private + */ + function toUpperCaseFn(match, value) { + return value.toLocaleUpperCase(); + } + + /** + * Callback method for regexp used in camelCaseToDashes method + * @method toUpperCaseFn + * @param {string} match + * @param {string} value + * @return {string} + * @member ns.util.string + * @static + * @private + */ + function toLowerCaseFn(match, value) { + return "-" + value.toLowerCase(); + } + + /** + * Changes dashes string to camel case string + * @method firstToUpperCase + * @param {string} str + * @return {string} + * @member ns.util.string + * @static + */ + function dashesToCamelCase(str) { + return str.replace(DASH_TO_UPPER_CASE_REGEXP, toUpperCaseFn); + } + + /** + * Changes camel case string to dashes string + * @method camelCaseToDashes + * @param {string} str + * @return {string} + * @member ns.util.string + * @static + */ + function camelCaseToDashes(str) { + return str.replace(UPPER_TO_DASH_CASE_REGEXP, toLowerCaseFn); + } + + /** + * Changes the first char in string to uppercase + * @method firstToUpperCase + * @param {string} str + * @return {string} + * @member ns.util.string + * @static + */ + function firstToUpperCase(str) { + return str.charAt(0).toLocaleUpperCase() + str.substring(1); + } + + /** + * Map different types to number if is possible. + * @param {string|*} x + * @return {*} + */ + function mapToNumber(x) { + var parsed; + + if (x && (x + "").indexOf("%") === -1) { + parsed = parseInt(x, 10); + if (isNaN(parsed)) { + parsed = null; + } + return parsed; + } + return x; + } + + /** + * Parses comma separated string to array + * @method parseProperty + * @param {string} property + * @return {Array} containing number or null + * @member ns.util.string + * @static + */ + function parseProperty(property) { + var arrayProperty; + + if (typeof property === "string") { + arrayProperty = property.split(","); + } else { + arrayProperty = property || []; + } + + return arrayUtil.map(arrayProperty, mapToNumber); + } + + ns.util.string = { + dashesToCamelCase: dashesToCamelCase, + camelCaseToDashes: camelCaseToDashes, + firstToUpperCase: firstToUpperCase, + parseProperty: parseProperty + }; + }()); + +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/*global window, ns, define */ +/* + * @author Jadwiga Sosnowska + * @author Krzysztof Antoszek + * @author Maciej Moczulski + * @author Piotr Karny + */ +(function (window, ns) { + "use strict"; + + var DOM = ns.util.DOM, + stringUtil = ns.util.string, + appStyleSheet; + + /** + * Returns css property for element + * @method getCSSProperty + * @param {HTMLElement} element + * @param {string} property + * @param {string|number|null} [def=null] default returned value + * @param {"integer"|"float"|null} [type=null] auto type casting + * @return {string|number|null} + * @member ns.util.DOM + * @static + */ + function getCSSProperty(element, property, def, type) { + var style = window.getComputedStyle(element), + value, + result = def; + + if (style) { + value = style.getPropertyValue(property); + if (value) { + switch (type) { + case "integer": + value = parseInt(value, 10); + if (!isNaN(value)) { + result = value; + } + break; + case "float": + value = parseFloat(value); + if (!isNaN(value)) { + result = value; + } + break; + default: + result = value; + break; + } + } + } + return result; + } + + /** + * Convert string to float or integer + * @param {string} value + * @return {number} + */ + function convertToNumber(value) { + if ((value + "").indexOf(".") > -1) { + return parseFloat(value); + } + return parseInt(value, 10); + } + + /** + * Extracts css properties from computed css for an element. + * The properties values are applied to the specified + * properties list (dictionary) + * @method extractCSSProperties + * @param {HTMLElement} element + * @param {Object} properties + * @param {?string} [pseudoSelector=null] + * @param {boolean} [noConversion=false] + * @member ns.util.DOM + * @static + */ + function extractCSSProperties(element, properties, pseudoSelector, noConversion) { + var style = window.getComputedStyle(element, pseudoSelector), + property, + value, + newValue; + + for (property in properties) { + if (properties.hasOwnProperty(property)) { + value = style.getPropertyValue(property); + newValue = convertToNumber(value); + + if (!isNaN(newValue) || !noConversion) { + value = newValue; + } + + properties[property] = value; + } + } + } + + function getOffset(element, props, pseudoSelector, force, offsetProperty) { + var originalDisplay, + originalVisibility, + originalPosition, + offsetValue, + style = element.style; + + if (style.display !== "none") { + extractCSSProperties(element, props, pseudoSelector, true); + offsetValue = element[offsetProperty]; + } else if (force) { + originalDisplay = style.display; + originalVisibility = style.visibility; + originalPosition = style.position; + + style.display = "block"; + style.visibility = "hidden"; + style.position = "relative"; + + extractCSSProperties(element, props, pseudoSelector, true); + offsetValue = element[offsetProperty]; + + style.display = originalDisplay; + style.visibility = originalVisibility; + style.position = originalPosition; + } + return offsetValue; + } + + /** + * Returns elements height from computed style + * @method getElementHeight + * @param {HTMLElement} element + * if null then the "inner" value is assigned + * @param {"outer"|null} [type=null] + * @param {boolean} [includeOffset=false] + * @param {boolean} [includeMargin=false] + * @param {?string} [pseudoSelector=null] + * @param {boolean} [force=false] check even if element is hidden + * @return {number} + * @member ns.util.DOM + * @static + */ + function getElementHeight(element, type, includeOffset, includeMargin, pseudoSelector, force) { + var height = 0, + outer = (type && type === "outer") || false, + offsetHeight, + property, + props = { + "height": 0, + "margin-top": 0, + "margin-bottom": 0, + "padding-top": 0, + "padding-bottom": 0, + "border-top-width": 0, + "border-bottom-width": 0, + "box-sizing": "" + }; + + if (element) { + offsetHeight = getOffset(element, props, pseudoSelector, force, "offsetHeight"); + + for (property in props) { + if (props.hasOwnProperty(property) && property !== "box-sizing") { + props[property] = convertToNumber(props[property]); + } + } + + height += props["height"]; + + if (props["box-sizing"] !== "border-box") { + height += props["padding-top"] + props["padding-bottom"]; + } + + if (includeOffset) { + height = offsetHeight; + } else if (outer && props["box-sizing"] !== "border-box") { + height += props["border-top-width"] + props["border-bottom-width"]; + } + + if (includeMargin) { + height += Math.max(0, props["margin-top"]) + Math.max(0, props["margin-bottom"]); + } + } + return height; + } + + /** + * Returns elements width from computed style + * @method getElementWidth + * @param {HTMLElement} element + * if null then the "inner" value is assigned + * @param {"outer"|null} [type=null] + * @param {boolean} [includeOffset=false] + * @param {boolean} [includeMargin=false] + * @param {?string} [pseudoSelector=null] + * @param {boolean} [force=false] check even if element is hidden + * @return {number} + * @member ns.util.DOM + * @static + */ + function getElementWidth(element, type, includeOffset, includeMargin, pseudoSelector, force) { + var width = 0, + value, + offsetWidth, + property, + outer = (type && type === "outer") || false, + props = { + "width": 0, + "margin-left": 0, + "margin-right": 0, + "padding-left": 0, + "padding-right": 0, + "border-left-width": 0, + "border-right-width": 0, + "box-sizing": "" + }; + + if (element) { + offsetWidth = getOffset(element, props, pseudoSelector, force, "offsetWidth"); + + for (property in props) { + if (props.hasOwnProperty(property) && property !== "box-sizing") { + value = parseFloat(props[property]); + props[property] = value; + } + } + + width += props["width"]; + if (props["box-sizing"] !== "border-box") { + width += props["padding-left"] + props["padding-right"]; + } + + if (includeOffset) { + width = offsetWidth; + } else if (outer && props["box-sizing"] !== "border-box") { + width += props["border-left-width"] + props["border-right-width"]; + } + + if (includeMargin) { + width += Math.max(0, props["margin-left"]) + Math.max(0, props["margin-right"]); + } + } + return width; + } + + /** + * Returns offset of element + * @method getElementOffset + * @param {HTMLElement} element + * @return {Object} + * @member ns.util.DOM + * @static + */ + function getElementOffset(element) { + var left = 0, + top = 0, + loopElement = element; + + do { + top += loopElement.offsetTop; + left += loopElement.offsetLeft; + loopElement = loopElement.offsetParent; + } while (loopElement !== null); + + return { + top: top, + left: left + }; + } + + /** + * Check if element occupies place at view + * @method isOccupiedPlace + * @param {HTMLElement} element + * @return {boolean} + * @member ns.util.DOM + * @static + */ + function isOccupiedPlace(element) { + return !(element.offsetWidth <= 0 && element.offsetHeight <= 0); + } + + /** + * Set values for element with prefixes for browsers + * @method setPrefixedStyle + * @param {HTMLElement | CSSStyleRule} elementOrRule + * @param {string} property + * @param {string|Object|null} value + * @member ns.util.DOM + * @static + */ + function setPrefixedStyle(elementOrRule, property, value) { + var style = elementOrRule.style, + propertyForPrefix = property, + values = (typeof value !== "object") ? { + webkit: value, + moz: value, + o: value, + ms: value, + normal: value + } : value; + + style.setProperty(property, values.normal); + style.setProperty("-webkit-" + propertyForPrefix, values.webkit); + style.setProperty("-moz-" + propertyForPrefix, values.moz); + style.setProperty("-o-" + propertyForPrefix, values.o); + style.setProperty("-ms-" + propertyForPrefix, values.ms); + } + + /** + * Get value from element with prefixes for browsers + * @method getCSSProperty + * @param {string} value + * @return {Object} + * @member ns.util.DOM + * @static + */ + function getPrefixedValue(value) { + return { + webkit: "-webkit-" + value, + moz: "-moz-" + value, + o: "-ms-" + value, + ms: "-o-" + value, + normal: value + }; + } + + /** + * Returns style value for css property with browsers prefixes + * @method getPrefixedStyle + * @param {HTMLStyle} styles + * @param {string} property + * @return {Object} + * @member ns.util.DOM + * @static + */ + function getPrefixedStyleValue(styles, property) { + var prefixedProperties = getPrefixedValue(property), + value, + key; + + for (key in prefixedProperties) { + if (prefixedProperties.hasOwnProperty(key)) { + value = styles[prefixedProperties[key]]; + if (value && value !== "none") { + return value; + } + } + } + return value; + } + + /** + * Returns size (width, height) as CSS string + * @method toCSSSize + * @param {string|Array} size has to be comma separated string (eg. "10,100") or array with 2 + * elements + * @return {string} if not enough arguments the method returns empty string + * @member ns.util.DOM + * @static + */ + function toCSSSize(size) { + var cssSize = "", + arraySize = stringUtil.parseProperty(size); + + if (arraySize && arraySize.length === 2) { + cssSize = "width: " + arraySize[0] + "px; " + + "height: " + arraySize[1] + "px;"; + } + + return cssSize; + } + + /** + * Set CSS styles for pseudo class selector. + * @method setStylesForPseudoClass + * @param {string} selector selector of elements + * @param {string} pseudoClassName CSS pseudo class name to set, for example after, before + * @param {Object} cssValues object with styles to set + * @return {number?} return index of inserted rule + * @member ns.util.DOM + * @static + */ + function setStylesForPseudoClass(selector, pseudoClassName, cssValues) { + var cssValuesArray = [], + headElement, + styleElement, + name; + + // create style element on first use + if (!appStyleSheet) { + headElement = document.head || document.getElementsByTagName("head")[0]; + styleElement = document.createElement("style"); + styleElement.type = "text/css"; + headElement.appendChild(styleElement); + appStyleSheet = styleElement.sheet; + } + + for (name in cssValues) { + if (cssValues.hasOwnProperty(name)) { + cssValuesArray.push(name + ": " + cssValues[name]); + } + } + + if (cssValuesArray.length) { + return appStyleSheet.addRule(selector + "::" + pseudoClassName, cssValuesArray.join("; ")); + } + + return null; + } + + /** + * Remove CSS rule from sheet. + * @method removeCSSRule + * @param {number} ruleIndex Index of rule to remove + * @static + */ + function removeCSSRule(ruleIndex) { + + // create style element on first use + if (appStyleSheet) { + appStyleSheet.deleteRule(ruleIndex); + } + } + + // assign methods to namespace + DOM.getCSSProperty = getCSSProperty; + DOM.extractCSSProperties = extractCSSProperties; + DOM.getElementHeight = getElementHeight; + DOM.getElementWidth = getElementWidth; + DOM.getElementOffset = getElementOffset; + DOM.isOccupiedPlace = isOccupiedPlace; + DOM.setPrefixedStyle = setPrefixedStyle; + DOM.getPrefixedValue = getPrefixedValue; + DOM.getPrefixedStyleValue = getPrefixedStyleValue; + DOM.toCSSSize = toCSSSize; + DOM.setStylesForPseudoClass = setStylesForPseudoClass; + DOM.removeCSSRule = removeCSSRule; + + }(window, ns)); + +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/*global window, ns, define */ +/** + * #Set Utility + * + * Own implementation of ECMAScript Set. + * + * @class ns.util.Set + */ +(function (window, ns) { + "use strict"; + var Set = function () { + this._data = []; + }; + + Set.prototype = { + /** + * Add one or many arguments to set + * @method add + * @member ns.util.Set + */ + add: function () { + var data = this._data; + + this._data = data.concat.apply(data, [].slice.call(arguments)) + .filter(function (item, pos, array) { + return array.indexOf(item) === pos; + }); + }, + /** + * Remove all items from set + * @method clear + * @member ns.util.Set + */ + clear: function () { + this._data = []; + }, + /** + * delete one item from set + * @method delete + * @param {*} item + * @member ns.util.Set + */ + delete: function (item) { + var data = this._data, + index = data.indexOf(item); + + if (index > -1) { + data.splice(index, 1); + } + }, + /** + * Check that item exists in set + * @method has + * @param {Object} item + * @member ns.util.Set + * @return {boolean} + */ + has: function (item) { + return this._data.indexOf(item) > -1; + }, + /** + * Iterate on each set elements + * @method forEach + * @param {Function} cb + * @member ns.util.Set + */ + forEach: function (cb) { + this._data.forEach(cb); + } + }; + + // for tests + ns.util._Set = Set; + ns.util.Set = window.Set || Set; + + }(window, ns)); + +/*global window, ns, define, ns */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (c) 2010 - 2014 Samsung Electronics Co., Ltd. + * License : MIT License V2 + */ +/** + * #Namespace For Widgets + * Namespace For Widgets + * @author Krzysztof Antoszek + * @class ns.widget + */ +(function (document) { + "use strict"; + var engine = ns.engine, + eventType = engine.eventType, + widget = { + /** + * Get bound widget for element + * @method getInstance + * @static + * @param {HTMLElement|string} element + * @param {string} type widget name + * @return {?Object} + * @member ns.widget + */ + getInstance: engine.getBinding, + /** + * Returns Get all bound widget for element or id gives as parameter + * @method getAllInstances + * @param {HTMLElement|string} element + * @return {?Object} + * @static + * @member ns.widget + */ + getAllInstances: engine.getAllBindings + }; + + function mapWidgetDefinition(name, element, options) { + var widgetParams = { + name: name, + element: element, + options: options + } + + return widgetParams; + } + + function widgetConstructor(name, element, options) { + var widgetParams = mapWidgetDefinition(name, element, options); + + return engine.instanceWidget(widgetParams.element, widgetParams.name, widgetParams.options); + } + + /** + * Register simple widget constructor in namespace + * @param {Event} event + */ + function defineWidget(event) { + var definition = event.detail, + name = definition.name; + + ns.widget[name] = widgetConstructor.bind(null, name); + } + + /** + * Remove event listeners on framework destroy + */ + function destroy() { + document.removeEventListener(eventType.WIDGET_DEFINED, defineWidget, true); + document.removeEventListener(eventType.DESTROY, destroy, false); + } + + document.addEventListener(eventType.WIDGET_DEFINED, defineWidget, true); + document.addEventListener(eventType.DESTROY, destroy, false); + + /** @namespace ns.widget */ + ns.widget = widget; + }(window.document)); + +/*global window, ns, define */ +/*jslint nomen: true */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * #BaseWidget + * Prototype class of widget + * + * ## How to invoke creation of widget from JavaScript + * + * To build and initialize widget in JavaScript you have to use method + * {@link ns.engine#instanceWidget}. First argument for method is HTMLElement, which specifies the + * element of widget. Second parameter is name of widget to create. + * + * If you load jQuery before initializing tau library, you can use standard jQuery UI Widget + * notation. + * + * ### Examples + * #### Build widget from JavaScript + * + * @example + * var element = document.getElementById("id"), + * ns.engine.instanceWidget(element, "Button"); + * + * #### Build widget from jQuery + * + * @example + * var element = $("#id").button(); + * + * ## How to create new widget + * + * @example + * (function (ns) { + * "use strict"; + * * var BaseWidget = ns.widget.BaseWidget, // create alias to main objects + * ... + * arrayOfElements, // example of private property, common for all instances of widget + * Button = function () { // create local object with widget + * ... + * }, + * prototype = new BaseWidget(); // add ns.widget.BaseWidget as prototype to widget's + * object, for better minification this should be assign to local variable and next + * variable should be assign to prototype of object. + * + * function closestEnabledButton(element) { // example of private method + * ... + * } + * ... + * + * prototype.options = { //add default options to be read from data- attributes + * theme: "s", + * ... + * }; + * + * prototype._build = function (template, element) { + * // method called when the widget is being built, should contain all HTML + * // manipulation actions + * ... + * return element; + * }; + * + * prototype._init = function (element) { + * // method called during initialization of widget, should contain all actions + * // necessary fastOn application start + * ... + * return element; + * }; + * + * prototype._bindEvents = function (element) { + * // method to bind all events, should contain all event bindings + * ... + * }; + * + * prototype._enable = function (element) { + * // method called during invocation of enable() method + * ... + * }; + * + * prototype._disable = function (element) { + * // method called during invocation of disable() method + * ... + * }; + * + * prototype.refresh = function (element) { + * // example of public method + * ... + * }; + * + * prototype._refresh = function () { + * // example of protected method + * ... + * }; + * + * Button.prototype = prototype; + * + * engine.defineWidget( // define widget + * "Button", //name of widget + * "[data-role='button'],button,[type='button'],[type='submit'],[type='reset']", + * //widget's selector + * [ // public methods, here should be list all public method + * "enable", + * "disable", + * "refresh" + * ], + * Button, // widget's object + * "mobile" // widget's namespace + * ); + * ns.widget.Button = Button; + * * }(ns)); + * @author Jadwiga Sosnowska + * @author Krzysztof Antoszek + * @author Tomasz Lukawski + * @author Przemyslaw Ciezkowski + * @author Maciej Urbanski + * @author Piotr Karny + * @author MichaÅ‚ Szepielak + * @class ns.widget.BaseWidget + */ +(function (document, ns) { + "use strict"; + var slice = [].slice, + /** + * Alias to ns.engine + * @property {ns.engine} engine + * @member ns.widget.BaseWidget + * @private + * @static + */ + engine = ns.engine, + engineDataTau = engine.dataTau, + util = ns.util, + /** + * Alias to {@link ns.event} + * @property {Object} eventUtils + * @member ns.widget.BaseWidget + * @private + * @static + */ + eventUtils = ns.event, + /** + * Alias to {@link ns.util.DOM} + * @property {Object} domUtils + * @private + * @static + */ + domUtils = util.DOM, + utilString = util.string, + /** + * Alias to {@link ns.util.object} + * @property {Object} objectUtils + * @private + * @static + */ + objectUtils = util.object, + Set = util.Set, + BaseWidget = function () { + this.flowState = "created"; + return this; + }, + getNSData = domUtils.getNSData, + prototype = {}, + /** + * Property with string represent function type + * (for better minification) + * @property {string} [TYPE_FUNCTION="function"] + * @private + * @static + * @readonly + */ + TYPE_FUNCTION = "function", + disableClass = "ui-state-disabled", + ariaDisabled = "aria-disabled", + __callbacks; + + BaseWidget.classes = { + disable: disableClass + }; + + prototype._configureDefinition = function (definition) { + var self = this, + definitionName, + definitionNamespace; + + if (definition) { + definitionName = definition.name; + definitionNamespace = definition.namespace; + /** + * Name of the widget + * @property {string} name + * @member ns.widget.BaseWidget + */ + self.name = definitionName; + + /** + * Name of the widget (in lower case) + * @property {string} widgetName + * @member ns.widget.BaseWidget + */ + self.widgetName = definitionName; + + /** + * Namespace of widget events + * @property {string} widgetEventPrefix + * @member ns.widget.BaseWidget + */ + self.widgetEventPrefix = definitionName.toLowerCase(); + + /** + * Namespace of the widget + * @property {string} namespace + * @member ns.widget.BaseWidget + */ + self.namespace = definitionNamespace; + + /** + * Full name of the widget + * @property {string} widgetFullName + * @member ns.widget.BaseWidget + */ + self.widgetFullName = ((definitionNamespace ? definitionNamespace + "-" : "") + + definitionName).toLowerCase(); + /** + * Id of widget instance + * @property {string} id + * @member ns.widget.BaseWidget + */ + self.id = ns.getUniqueId(); + + /** + * Widget's selector + * @property {string} selector + * @member ns.widget.BaseWidget + */ + self.selector = definition.selector; + } + }; + + /** + * Protected method configuring the widget + * @method _configure + * @member ns.widget.BaseWidget + * @protected + * @template + * @ignore + */ + /** + * Configures widget object from definition. + * + * It calls such methods as #\_getCreateOptions and #\_configure. + * @method configure + * @param {Object} definition + * @param {string} definition.name Name of the widget + * @param {string} definition.selector Selector of the widget + * @param {HTMLElement} element Element of widget + * @param {Object} options Configure options + * @member ns.widget.BaseWidget + * @return {ns.widget.BaseWidget} + * @ignore + */ + prototype.configure = function (definition, element, options) { + var self = this; + /** + * Object with options for widget + * @property {Object} [options={}] + * @member ns.widget.BaseWidget + */ + + self.flowState = "configuring"; + + self.options = self.options || {}; + /** + * Base element of widget + * @property {?HTMLElement} [element=null] + * @member ns.widget.BaseWidget + */ + self.element = self.element || null; + + self._configureDefinition(definition); + + if (typeof self._configure === TYPE_FUNCTION) { + self._configure(element); + } + + self.isCustomElement = !!element.createdCallback; + + self._getCreateOptions(element); + + objectUtils.fastMerge(self.options, options); + + self.flowState = "configured"; + }; + + /** + * Reads data-* attributes and save to options object. + * @method _getCreateOptions + * @param {HTMLElement} element Base element of the widget + * @return {Object} + * @member ns.widget.BaseWidget + * @protected + */ + prototype._getCreateOptions = function (element) { + var self = this, + options = self.options, + tag = element.localName.toLowerCase(); + + if (options) { + Object.keys(options).forEach(function (option) { + var attributeName = utilString.camelCaseToDashes(option), + baseValue = getNSData(element, attributeName, true), + prefixedValue = getNSData(element, attributeName); + + if (prefixedValue !== null) { + options[option] = prefixedValue; + } else { + if (typeof options[option] === "boolean") { + self._readBooleanOptionFromElement(element, option); + } + } + + if (option === "type" && tag === "input") { // don't set conflicting props + return; + } + + if (baseValue !== null) { + options[option] = baseValue; + } + }); + } + return options; + }; + + /** + * Protected method building the widget + * @method _build + * @param {HTMLElement} element + * @return {HTMLElement} widget's element + * @member ns.widget.BaseWidget + * @protected + * @template + */ + /** + * Builds widget. + * + * It calls method #\_build. + * + * Before starting building process, the event beforecreate with + * proper prefix defined in variable widgetEventPrefix is triggered. + * @method build + * @param {HTMLElement} element Element of widget before building process + * @return {HTMLElement} Element of widget after building process + * @member ns.widget.BaseWidget + * @ignore + */ + prototype.build = function (element) { + var self = this, + id, + node, + dataBuilt = element.getAttribute(engineDataTau.built), + dataName = element.getAttribute(engineDataTau.name); + + eventUtils.trigger(element, self.widgetEventPrefix + "beforecreate"); + + self.flowState = "building"; + + id = element.id; + if (id) { + self.id = id; + } else { + element.id = self.id; + } + + if (typeof self._build === TYPE_FUNCTION) { + node = self._build(element); + } else { + node = element; + } + + self._setBooleanOptions(element); + + // Append current widget name to data-tau-built and data-tau-name attributes + dataBuilt = !dataBuilt ? self.name : dataBuilt + engineDataTau.separator + self.name; + dataName = !dataName ? self.name : dataName + engineDataTau.separator + self.name; + + element.setAttribute(engineDataTau.built, dataBuilt); + element.setAttribute(engineDataTau.name, dataName); + + self.flowState = "built"; + return node; + }; + + /** + * Protected method initializing the widget + * @method _init + * @param {HTMLElement} element + * @member ns.widget.BaseWidget + * @template + * @protected + */ + /** + * Initializes widget. + * + * It calls method #\_init. + * @method init + * @param {HTMLElement} element Element of widget before initialization + * @member ns.widget.BaseWidget + * @return {ns.widget.BaseWidget} + * @ignore + */ + prototype.init = function (element) { + var self = this; + + self.id = element.id; + + self.flowState = "initiating"; + + if (typeof self._init === TYPE_FUNCTION) { + self._init(element); + } + + if (element.getAttribute("disabled") || self.options.disabled === true) { + self.disable(); + } else { + self.enable(); + } + self.flowState = "initiated"; + return self; + }; + + /** + * Returns base element widget + * @member ns.widget.BaseWidget + * @return {HTMLElement|null} + * @instance + */ + prototype.getContainer = function () { + var self = this; + + if (typeof self._getContainer === TYPE_FUNCTION) { + return self._getContainer(); + } + return self.element; + }; + + /** + * Bind widget events attached in init mode + * @method _bindEvents + * @param {HTMLElement} element Base element of widget + * @member ns.widget.BaseWidget + * @template + * @protected + */ + /** + * Binds widget events. + * + * It calls such methods as #\_buildBindEvents and #\_bindEvents. + * At the end of binding process, the event "create" with proper + * prefix defined in variable widgetEventPrefix is triggered. + * @method bindEvents + * @param {HTMLElement} element Base element of the widget + * @param {boolean} onlyBuild Inform about the type of bindings: build/init + * @member ns.widget.BaseWidget + * @return {ns.widget.BaseWidget} + * @ignore + */ + prototype.bindEvents = function (element, onlyBuild) { + var self = this, + dataBound = element.getAttribute(engineDataTau.bound); + + if (!onlyBuild) { + dataBound = !dataBound ? self.name : dataBound + engineDataTau.separator + self.name; + element.setAttribute(engineDataTau.bound, dataBound); + } + if (typeof self._buildBindEvents === TYPE_FUNCTION) { + self._buildBindEvents(element); + } + if (!onlyBuild && typeof self._bindEvents === TYPE_FUNCTION) { + self._bindEvents(element); + } + + self.trigger(self.widgetEventPrefix + "create", self); + + return self; + }; + + /** + * Event triggered when method focus is called + * @event taufocus + * @member ns.widget.BaseWidget + */ + + /** + * Focus widget's element. + * + * This function calls function focus on element and if it is known + * the direction of event, the proper css classes are added/removed. + * @method focus + * @param {Object} options The options of event. + * @param {HTMLElement} options.previousElement Element to blur + * @param {HTMLElement} options.element Element to focus + * @member ns.widget.BaseWidget + */ + prototype.focus = function (options) { + var self = this, + element = self.element, + blurElement, + blurWidget; + + options = options || {}; + + blurElement = options.previousElement; + // we try to blur element, which has focus previously + if (blurElement) { + blurWidget = engine.getBinding(blurElement); + // call blur function on widget + if (blurWidget) { + options = objectUtils.merge({}, options, {element: blurElement}); + blurWidget.blur(options); + } else { + // or on element, if widget does not exist + blurElement.blur(); + } + } + + options = objectUtils.merge({}, options, {element: element}); + + // set focus on element + eventUtils.trigger(document, "taufocus", options); + element.focus(); + + return true; + }; + + /** + * Event triggered then method blur is called. + * @event taublur + * @member ns.widget.BaseWidget + */ + + /** + * Blur widget's element. + * + * This function calls function blur on element and if it is known + * the direction of event, the proper css classes are added/removed. + * @method blur + * @param {Object} options The options of event. + * @param {HTMLElement} options.element Element to blur + * @member ns.widget.BaseWidget + */ + prototype.blur = function (options) { + var self = this, + element = self.element; + + options = objectUtils.merge({}, options, {element: element}); + + // blur element + eventUtils.trigger(document, "taublur", options); + element.blur(); + return true; + }; + + /** + * Protected method destroying the widget + * @method _destroy + * @template + * @protected + * @member ns.widget.BaseWidget + */ + /** + * Destroys widget. + * + * It calls method #\_destroy. + * + * At the end of destroying process, the event "destroy" with proper + * prefix defined in variable widgetEventPrefix is triggered and + * the binding set in engine is removed. + * @method destroy + * @param {HTMLElement} element Base element of the widget + * @member ns.widget.BaseWidget + */ + prototype.destroy = function (element) { + var self = this; + + element = element || self.element; + + // the widget is in during destroy process + self.flowState = "destroying"; + + if (typeof self._destroy === TYPE_FUNCTION) { + self._destroy(element); + } + if (self.element) { + self.trigger(self.widgetEventPrefix + "destroy"); + } + if (element) { + engine.removeBinding(element, self.name); + } + // the widget was destroyed + self.flowState = "destroyed"; + }; + + /** + * Protected method disabling the widget + * @method _disable + * @protected + * @member ns.widget.BaseWidget + * @template + */ + /** + * Disables widget. + * + * It calls method #\_disable. + * @method disable + * @member ns.widget.BaseWidget + * @return {ns.widget.BaseWidget} + */ + prototype.disable = function () { + var self = this, + args = slice.call(arguments), + element = self.element; + + element.classList.add(disableClass); + element.setAttribute(ariaDisabled, true); + + if (typeof self._disable === TYPE_FUNCTION) { + args.unshift(element); + self._disable.apply(self, args); + } + return this; + }; + + /** + * Check if widget is disabled. + * @method isDisabled + * @member ns.widget.BaseWidget + * @return {boolean} Returns true if widget is disabled + */ + prototype.isDisabled = function () { + var self = this; + + return self.element.getAttribute("disabled") || self.options.disabled === true; + }; + + /** + * Protected method enabling the widget + * @method _enable + * @protected + * @member ns.widget.BaseWidget + * @template + */ + /** + * Enables widget. + * + * It calls method #\_enable. + * @method enable + * @member ns.widget.BaseWidget + * @return {ns.widget.BaseWidget} + */ + prototype.enable = function () { + var self = this, + args = slice.call(arguments), + element = self.element; + + element.classList.remove(disableClass); + element.setAttribute(ariaDisabled, false); + + if (typeof self._enable === TYPE_FUNCTION) { + args.unshift(element); + self._enable.apply(self, args); + } + return this; + }; + + /** + * Protected method causing the widget to refresh + * @method _refresh + * @protected + * @member ns.widget.BaseWidget + * @template + */ + /** + * Refreshes widget. + * + * It calls method #\_refresh. + * @method refresh + * @member ns.widget.BaseWidget + * @return {ns.widget.BaseWidget} + */ + prototype.refresh = function () { + var self = this; + + if (typeof self._refresh === TYPE_FUNCTION) { + self._refresh.apply(self, arguments); + } + return self; + }; + + /** + * Reads class based on name conversion option value, for all options which have boolean value + * we can read option value by check that exists classname connected with option name. To + * correct use this method is required define in widget property _classesPrefix. + * + * For example for option middle in Button widget we will check existing of class + * ui-btn-middle. + * + * @method _readBooleanOptionFromElement + * @param {HTMLElement} element Main element of widget + * @param {string} name Name of option which should be used + * @return {boolean} + * @member ns.widget.BaseWidget + * @protected + */ + prototype._readBooleanOptionFromElement = function (element, name) { + var classesPrefix = this._classesPrefix, + className; + + if (classesPrefix) { + className = classesPrefix + utilString.camelCaseToDashes(name); + this.options[name] = element.classList.contains(className); + } + }; + + /** + * Sets or removes class based on name conversion option, for all options which have boolean + * value we can just set classname which is converted from camel case to dash style. + * To correct use this method is required define in widget property _classesPrefix. + * + * For example for option middle in Button widget we will set or remove class ui-btn-middle. + * + * @method _setBooleanOption + * @param {HTMLElement} element Main element of widget + * @param {string} name Name of option which should be used + * @param {boolean} value New value of option to set + * @member ns.widget.BaseWidget + * @protected + * @return {false} always return false to block refreshing + */ + prototype._setBooleanOption = function (element, name, value) { + var classesPrefix = this._classesPrefix, + className; + + if (classesPrefix) { + className = classesPrefix + utilString.camelCaseToDashes(name); + element.classList.toggle(className, value); + } + + // we don't need refresh, always can return false + return false; + }; + + /** + * For each options which has boolean value set or remove connected class. + * + * @method _setBooleanOptions + * @param {HTMLElement} element Base element of the widget + * @return {Object} + * @member ns.widget.BaseWidget + * @protected + */ + prototype._setBooleanOptions = function (element) { + var self = this, + classesPrefix = self._classesPrefix, + options = self.options; + + if (classesPrefix && options !== undefined) { + Object.keys(options).forEach(function (option) { + if (typeof options[option] === "boolean") { + options[option] = self._setBooleanOption(element, option, options[option]); + } + }); + } + return options; + }; + + prototype._processOptionObject = function (firstArgument) { + var self = this, + key, + partResult, + refresh = false; + + for (key in firstArgument) { + if (firstArgument.hasOwnProperty(key)) { + partResult = self._oneOption(key, firstArgument[key]); + if (key !== undefined && firstArgument[key] !== undefined) { + refresh = refresh || partResult; + } + } + } + return refresh; + }; + + /** + * Gets or sets options of the widget. + * + * This method can work in many context. + * + * If first argument is type of object them, method set values for options given in object. + * Keys of object are names of options and values from object are values to set. + * + * If you give only one string argument then method return value for given option. + * + * If you give two arguments and first argument will be a string then second argument will be + * intemperate as value to set. + * + * @method option + * @param {string|Object} [name] name of option + * @param {*} [value] value to set + * @member ns.widget.BaseWidget + * @return {*} return value of option or null if method is called in setter context + */ + prototype.option = function (name, value) { + var self = this, + firstArgument = name, + secondArgument = value, + result = null, + refresh = false; + + if (typeof firstArgument === "string") { + result = self._oneOption(firstArgument, secondArgument); + if (secondArgument !== undefined) { + refresh = result; + result = null; + } + } else if (typeof firstArgument === "object") { + refresh = self._processOptionObject(firstArgument); + } + if (refresh) { + self.refresh(); + } + return result; + }; + + /** + * Gets or sets one option of the widget. + * + * @method _oneOption + * @param {string} field + * @param {*} value + * @member ns.widget.BaseWidget + * @return {*} + * @protected + */ + prototype._oneOption = function (field, value) { + var self = this, + methodName, + refresh = false; + + if (value === undefined) { + methodName = "_get" + (field[0].toUpperCase() + field.slice(1)); + if (typeof self[methodName] === TYPE_FUNCTION) { + return self[methodName](); + } + return self.options[field]; + } + methodName = "_set" + (field[0].toUpperCase() + field.slice(1)); + if (typeof self[methodName] === TYPE_FUNCTION) { + refresh = self[methodName](self.element, value); + } else if (typeof value === "boolean") { + refresh = self._setBooleanOption(self.element, field, value); + } else { + self.options[field] = value; + if (self.element) { + self.element.setAttribute("data-" + (field.replace(/[A-Z]/g, function (c) { + return "-" + c.toLowerCase(); + })), value); + refresh = true; + } + } + return refresh; + }; + + /** + * Returns true if widget has bounded events. + * + * This methods enables to check if the widget has bounded + * events through the {@link ns.widget.BaseWidget#bindEvents} method. + * @method isBound + * @param {string} [type] Type of widget + * @member ns.widget.BaseWidget + * @ignore + * @return {boolean} true if events are bounded + */ + prototype.isBound = function (type) { + var element = this.element; + + type = type || this.name; + return element && element.hasAttribute(engineDataTau.bound) && + element.getAttribute(engineDataTau.bound).indexOf(type) > -1; + }; + + /** + * Returns true if widget is built. + * + * This methods enables to check if the widget was built + * through the {@link ns.widget.BaseWidget#build} method. + * @method isBuilt + * @param {string} [type] Type of widget + * @member ns.widget.BaseWidget + * @ignore + * @return {boolean} true if the widget was built + */ + prototype.isBuilt = function (type) { + var element = this.element; + + type = type || this.name; + return element && element.hasAttribute(engineDataTau.built) && + element.getAttribute(engineDataTau.built).indexOf(type) > -1; + }; + + /** + * Protected method getting the value of widget + * @method _getValue + * @return {*} + * @member ns.widget.BaseWidget + * @template + * @protected + */ + /** + * Protected method setting the value of widget + * @method _setValue + * @param {*} value + * @return {*} + * @member ns.widget.BaseWidget + * @template + * @protected + */ + /** + * Gets or sets value of the widget. + * + * @method value + * @param {*} [value] New value of widget + * @member ns.widget.BaseWidget + * @return {*} + */ + prototype.value = function (value) { + var self = this; + + if (value !== undefined) { + if (typeof self._setValue === TYPE_FUNCTION) { + return self._setValue(value); + } + return self; + } + if (typeof self._getValue === TYPE_FUNCTION) { + return self._getValue(); + } + return self; + }; + + /** + * Triggers an event on widget's element. + * + * @method trigger + * @param {string} eventName The name of event to trigger + * @param {?*} [data] additional Object to be carried with the event + * @param {boolean} [bubbles=true] Indicating whether the event + * bubbles up through the DOM or not + * @param {boolean} [cancelable=true] Indicating whether + * the event is cancelable + * @member ns.widget.BaseWidget + * @return {boolean} False, if any callback invoked preventDefault on event object + */ + prototype.trigger = function (eventName, data, bubbles, cancelable) { + return eventUtils.trigger(this.element, eventName, data, bubbles, cancelable); + }; + + /** + * Adds event listener to widget's element. + * @method on + * @param {string} eventName The name of event + * @param {Function} listener Function called after event will be trigger + * @param {boolean} [useCapture=false] useCapture Parameter of addEventListener + * @member ns.widget.BaseWidget + */ + prototype.on = function (eventName, listener, useCapture) { + eventUtils.on(this.element, eventName, listener, useCapture); + }; + + /** + * Removes event listener from widget's element. + * @method off + * @param {string} eventName The name of event + * @param {Function} listener Function call after event will be trigger + * @param {boolean} [useCapture=false] useCapture Parameter of addEventListener + * @member ns.widget.BaseWidget + */ + prototype.off = function (eventName, listener, useCapture) { + eventUtils.off(this.element, eventName, listener, useCapture); + }; + + prototype._framesFlow = function () { + var self = this, + args = slice.call(arguments), + func = args.shift(); + + if (typeof func === "function") { + func(); + } + if (func !== undefined) { + util.requestAnimationFrame(function frameFlowCallback() { + self._framesFlow.apply(self, args); + }); + } + }; + + function callbacksFilter(item) { + return !item.toRemove; + } + + function callbacksForEach(item) { + if (item.object[item.property] === item.value) { + util.requestAnimationFrame(item.callback.bind(item.object)); + item.toRemove = true; + } + } + + function _controlWaitFor() { + __callbacks.forEach(callbacksForEach); + __callbacks = __callbacks.filter(callbacksFilter); + if (__callbacks.length) { + util.requestAnimationFrame(_controlWaitFor); + } + } + + prototype._waitFor = function (property, value, callback) { + var self = this; + + if (self[property] === value) { + callback.call(self); + } else { + __callbacks = __callbacks || []; + __callbacks.push({ + object: self, + property: property, + value: value, + callback: callback + }); + } + _controlWaitFor(); + }; + + function readDOMElementStateClassList(element, stateObject) { + var classList = stateObject.classList; + + if (classList !== undefined) { + if (classList instanceof Set) { + classList.clear(); + } else { + classList = new Set(); + stateObject.classList = classList; + } + if (element.classList.length) { + classList.add.apply(classList, slice.call(element.classList)); + } + } + } + + function readDOMElementState(element, stateObject) { + readDOMElementStateClassList(element, stateObject); + if (stateObject.offsetWidth !== undefined) { + stateObject.offsetWidth = element.offsetWidth; + } + if (stateObject.style !== undefined) { + domUtils.extractCSSProperties(element, stateObject.style, null, true); + } + if (stateObject.children !== undefined) { + stateObject.children.forEach(function (child, index) { + readDOMElementState(element.children[index], child); + }); + } + } + + function render(stateObject, element, isChild) { + var recalculate = false; + + if (stateObject.classList !== undefined) { + slice.call(element.classList).forEach(function renderRemoveClassList(className) { + if (!stateObject.classList.has(className)) { + element.classList.remove(className); + recalculate = true; + } + }); + stateObject.classList.forEach(function renderAddClassList(className) { + if (!element.classList.contains(className)) { + element.classList.add(className); + recalculate = true; + } + }); + } + if (stateObject.style !== undefined) { + Object.keys(stateObject.style).forEach(function renderUpdateStyle(styleName) { + element.style[styleName] = stateObject.style[styleName]; + }); + } + if (stateObject.children !== undefined) { + stateObject.children.forEach(function renderChildren(child, index) { + render(child, element.children[index], true); + }); + } + if (recalculate && !isChild) { + util.requestAnimationFrame(readDOMElementState.bind(null, element, stateObject)); + } + } + + prototype._render = function (now) { + var self = this, + stateDOM = self._stateDOM, + element = self.element; + + if (now) { + render(stateDOM, element); + } else { + util.requestAnimationFrame(render.bind(null, stateDOM, element)); + } + }; + + prototype._initDOMstate = function () { + readDOMElementState(this.element, this._stateDOM); + }; + + prototype._togglePrefixedClass = function (stateDOM, prefix, name) { + var requireRefresh = false, + prefixedClassName = prefix + name; + + stateDOM.classList.forEach(function (className) { + if (className.indexOf(prefix) === 0 && prefixedClassName !== className) { + stateDOM.classList.delete(className); + requireRefresh = true; + } + }); + if (!stateDOM.classList.has(prefixedClassName)) { + stateDOM.classList.add(prefixedClassName); + requireRefresh = true; + } + return requireRefresh; + }; + + BaseWidget.prototype = prototype; + + // definition + ns.widget.BaseWidget = BaseWidget; + + }(window.document, ns)); + +/*global window, ns, define, ns */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * #Namespace For Core Widgets + * @author Krzysztof Antoszek + * @class ns.widget.core + */ +(function (document, ns) { + "use strict"; + ns.widget.core = ns.widget.core || {}; + }(window.document, ns)); + +/*global window, ns, define */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/*jslint nomen: true */ +/** + * # Page Widget + * Page is main element of application's structure. + * + * ## Default selectors + * In the Tizen Web UI framework the application page structure is based on a header, content and footer elements: + * + * - **The header** is placed at the top, and displays the page title and optionally buttons. + * - **The content** is the section below the header, showing the main content of the page. + * - **The footer** is a bottom part of page which can display for example buttons + * + * The following table describes the specific information for each section. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
SectionClassMandatoryDescription
Pageui-pageYesDefines the element as a page. + * + * The page widget is used to manage a single item in a page-based architecture. + * + * A page is composed of header (optional), content (mandatory), and footer (optional) elements.
ui-page-activeNoIf an application has a static start page, insert the ui-page-active class in the page element to + * speed up the application launch. The start page with the ui-page-active class can be displayed before + * the framework is fully loaded. + * + * If this class is not used, the framework inserts the class automatically to the first page of the + * application. + * + * However, this has a slowing effect on the application launch, because the page is displayed only after + * *the* framework is fully loaded.
Headerui-headerNoDefines the element as a header.
Contentui-contentYesDefines the element as content.
Footerui-footerNoDefines the element as a footer. + * + * The footer section is mostly used to include option buttons.
+ * + * All elements with class=ui-page will be become page widgets + * + * @example + * + *
+ *
+ *
+ *
+ *
+ * + * + *
+ *
+ *

Call menu

+ * + *
+ *
Content message
+ *
+ * + *
+ *
+ * + * ## Manual constructor + * For manual creation of page widget you can use constructor of widget from **tau** namespace: + * + * @example + * var pageElement = document.getElementById("page"), + * page = tau.widget.Page(buttonElement); + * + * Constructor has one require parameter **element** which are base **HTMLElement** to create widget. + * We recommend get + * this element by method *document.getElementById*. + * + * ## Multi-page Layout + * + * You can implement a template containing multiple page containers in the application index.html file. + * + * In the multi-page layout, the main page is defined with the ui-page-active class. + * If no page has the ui-page-active + * class, the framework automatically sets up the first page in the source order + * as the main page. You can improve the + * launch performance by explicitly defining the main page to be displayed first. + * If the application has to wait for + * the framework to set up the main page, the page is displayed with some delay + * only after the framework is fully + * loaded. + * + * You can link to internal pages by referring to the ID of the page. For example, to link to the page with an ID + * of + * two, the link element needs the href="#two" attribute in the code, as in the following example. + * + * @example + * + *
+ *
+ *
+ *
+ *
+ * + * + *
+ *
+ *
+ *
+ *
+ * + * To find the currently active page, use the ui-page-active class. + * + * ## Changing Pages + * ### Go to page in JavaScript + * To change page use method *tau.changePage* + * + * @example + * tau.changePage("page-two"); + * + * ### Back in JavaScript + * To back to previous page use method *tau.back* + * + * @example + * tau.back(); + * + * ## Transitions + * + * When changing the active page, you can use a page transition. + * + * Tizen Web UI Framework does not apply transitions by default. To set a custom transition effect, + * you must add the + * data-transition attribute to a link: + * + * @example + * I\'ll slide up + * + * To set a default custom transition effect for all pages, use the pageTransition property: + * + * @example + * tau.defaults.pageTransition = "slideup"; + * + * ### Transitions list + * + * - **none** no transition. + * - **slideup** Makes the content of the next page slide up, appearing to conceal the content of the previous page. + * + * ## Handling Page Events + * + * With page widget we have connected many of events. + * + * To handle page events, use the following code: + * + * @example + *
+ *
+ *
+ *
+ * + * + * + * To bind an event callback on the Back key, use the following code: + * + * Full list of available events is in [events list section](#events-list). + * + * To bind an event callback on the Back key, use the following code: + * + * @example + * + * + * + * ## Methods + * + * To call method on widget you can use tau API: + * + * @example + * var pageElement = document.getElementById("page"), + * page = tau.widget.Page(buttonElement); + * + * page.methodName(methodArgument1, methodArgument2, ...); + * + * @class ns.widget.core.Page + * @extends ns.widget.BaseWidget + * @component-selector .ui-page + * @component-type container-component + * @component-constraint 'popup', 'drawer', 'header', 'bottom-button' + * @component-attachable false + * @author Maciej Urbanski + * @author Piotr Karny + * @author Damian Osipiuk + */ +(function (document, ns) { + "use strict"; + /** + * Alias for {@link ns.widget.BaseWidget} + * @property {Object} BaseWidget + * @member ns.widget.core.Page + * @private + * @static + */ + var BaseWidget = ns.widget.BaseWidget, + /** + * Alias for {@link ns.util} + * @property {Object} util + * @member ns.widget.core.Page + * @private + * @static + */ + util = ns.util, + utilsDOM = util.DOM, + /** + * Alias for {@link ns.util.selectors} + * @property {Object} utilSelectors + * @member ns.widget.core.Page + * @private + * @static + */ + utilSelectors = util.selectors, + /** + * Alias for {@link ns.engine} + * @property {Object} engine + * @member ns.widget.core.Page + * @private + * @static + */ + engine = ns.engine, + + Page = function () { + var self = this; + /** + * Callback on resize + * @property {?Function} _contentFillAfterResizeCallback + * @private + * @member ns.widget.core.Page + */ + + self._contentFillAfterResizeCallback = null; + self._initialContentStyle = {}; + /** + * Options for widget. + * It is empty object, because widget Page does not have any options. + * @property {Object} options + * @member ns.widget.core.Page + */ + self.options = {}; + + self._contentStyleAttributes = ["height", "width", "minHeight", "marginTop", "marginBottom"]; + + self._ui = {}; + }, + /** + * Dictionary for page related event types + * @property {Object} EventType + * @member ns.widget.core.Page + * @static + */ + EventType = { + /** + * Triggered on the page we are transitioning to, + * after the transition animation has completed. + * @event pageshow + * @member ns.widget.core.Page + */ + SHOW: "pageshow", + /** + * Triggered on the page we are transitioning away from, + * after the transition animation has completed. + * @event pagehide + * @member ns.widget.core.Page + */ + HIDE: "pagehide", + /** + * Triggered when the page has been created in the DOM + * (for example, through Ajax) but before all widgets + * have had an opportunity to enhance the contained markup. + * @event pagecreate + * @member ns.widget.core.Page + */ + CREATE: "pagecreate", + /** + * Triggered when the page is being initialized, + * before most plugin auto-initialization occurs. + * @event pagebeforecreate + * @member ns.widget.core.Page + */ + BEFORE_CREATE: "pagebeforecreate", + /** + * Triggered on the page we are transitioning to, + * before the actual transition animation is kicked off. + * @event pagebeforeshow + * @member ns.widget.core.Page + */ + BEFORE_SHOW: "pagebeforeshow", + /** + * Triggered on the page we are transitioning away from, + * before the actual transition animation is kicked off. + * @event pagebeforehide + * @member ns.widget.core.Page + */ + BEFORE_HIDE: "pagebeforehide" + }, + /** + * Dictionary for page related css class names + * @property {Object} classes + * @member ns.widget.core.Page + * @static + * @readonly + */ + classes = { + uiPage: "ui-page", + /** + * Indicates active page + * @style ui-page-active + * @member ns.widget.core.Page + */ + uiPageActive: "ui-page-active", + uiSection: "ui-section", + uiHeader: "ui-header", + uiFooter: "ui-footer", + uiContent: "ui-content", + uiTitle: "ui-title", + uiPageScroll: "ui-scroll-on", + uiScroller: "ui-scroller" + }, + HEADER_SELECTOR = "header,[data-role='header'],." + classes.uiHeader, + FOOTER_SELECTOR = "footer,[data-role='footer'],." + classes.uiFooter, + //ui-indexscrollbar is needed as widget ads html markup at the + //same level as content, other wise page content is build on + //indexscrollbar element + CONTENT_SELECTOR = "[data-role='content'],." + classes.uiContent, + prototype = new BaseWidget(); + + Page.classes = classes; + Page.events = EventType; + + /** + * Configure default options for widget + * @method _configure + * @protected + * @member ns.widget.core.Page + */ + prototype._configure = function () { + var options = this.options; + /** + * Object with default options + * @property {Object} options + * @property {boolean|string|null} [options.header=false] Sets content of header. + * @property {boolean|string|null} [options.footer=false] Sets content of footer. + * @property {boolean} [options.autoBuildWidgets=false] Automatically build widgets inside page. + * @property {string} [options.content=null] Sets content of popup. + * @member ns.widget.core.Page + * @static + */ + + options.header = null; + options.footer = null; + options.content = null; + options.enablePageScroll = ns.getConfig("enablePageScroll"); + options.autoBuildWidgets = ns.getConfig("autoBuildOnPageChange"); + this.options = options; + }; + + /** + * Setup size of element to 100% of screen + * @method _contentFill + * @protected + * @member ns.widget.core.Page + */ + prototype._contentFill = function () { + var self = this, + element = self.element, + screenWidth = window.innerWidth, + screenHeight = window.innerHeight, + elementStyle = element.style, + ui = self._ui, + content = ui.content, + contentStyle, + header = ui.header, + top = 0, + bottom = 0, + footer = ui.footer; + + elementStyle.width = screenWidth + "px"; + elementStyle.height = screenHeight + "px"; + + if (content && !element.classList.contains("ui-page-flex")) { + contentStyle = content.style; + + if (header) { + top = utilsDOM.getElementHeight(header); + } + + if (footer) { + bottom = utilsDOM.getElementHeight(footer); + contentStyle.marginBottom = bottom + "px"; + contentStyle.paddingBottom = (-bottom) + "px"; + } + + if (!self.options.enablePageScroll) { + contentStyle.height = (screenHeight - top - bottom) + "px"; + } + } + }; + + prototype._storeContentStyle = function () { + var self = this, + initialContentStyle = self._initialContentStyle, + contentStyleAttributes = self._contentStyleAttributes, + content = self.element.querySelector("." + classes.uiContent), + contentStyle = content ? content.style : {}; + + contentStyleAttributes.forEach(function (name) { + initialContentStyle[name] = contentStyle[name]; + }); + }; + + /** + * Restore saved styles for content. + * Called on refresh or hide. + * @protected + */ + prototype._restoreContentStyle = function () { + var self = this, + initialContentStyle = self._initialContentStyle, + contentStyleAttributes = self._contentStyleAttributes, + content = self.element.querySelector("." + classes.uiContent), + contentStyle = content ? content.style : {}; + + contentStyleAttributes.forEach(function (name) { + contentStyle[name] = initialContentStyle[name]; + }); + }; + + /** + * Setter for footer option + * @method _setFooter + * @param {HTMLElement} element + * @param {string} value + * @protected + * @member ns.widget.core.Page + */ + prototype._setFooter = function (element, value) { + var self = this, + ui = self._ui, + footer = ui.footer; + + // footer element if footer does not exist and value is true or string + if (!footer && value) { + footer = document.createElement("footer"); + element.appendChild(footer); + ui.footer = footer; + } + if (footer) { + // remove child if footer does not exist and value is set to false + if (value === false) { + element.removeChild(footer); + ui.footer = null; + } else { + // if options is set to true, to string or not is set + // add class + footer.classList.add(classes.uiFooter); + // if is string fill content by string value + if (typeof value === "string") { + ui.footer.textContent = value; + } + } + // and remember options + self.options.footer = value; + } + }; + + /** + * Setter for header option + * @method _setHeader + * @param {HTMLElement} element + * @param {string} value + * @protected + * @member ns.widget.core.Page + */ + prototype._setHeader = function (element, value) { + var self = this, + ui = self._ui, + header = ui.header; + + // header element if header does not exist and value is true or string + if (!header && value) { + header = document.createElement("header"); + element.appendChild(header); + ui.header = header; + } + if (header) { + // remove child if header does not exist and value is set to false + if (value === false) { + element.removeChild(header); + ui.header = null; + } else { + // if options is set to true, to string or not is set + // add class + header.classList.add(classes.uiHeader); + // if is string fill content by string value + if (typeof value === "string") { + ui.header.textContent = value; + } + } + // and remember options + self.options.header = value; + } + }; + + /** + * Setter for content option + * @method _setContent + * @param {HTMLElement} element + * @param {string} value + * @protected + * @member ns.widget.core.Page + */ + prototype._setContent = function (element, value) { + var self = this, + ui = self._ui, + content = ui.content, + child = element.firstChild, + next; + + if (!content && value) { + content = document.createElement("div"); + while (child) { + next = child.nextSibling; + if (child !== ui.footer && child !== ui.header) { + content.appendChild(child); + } + child = next; + } + element.insertBefore(content, ui.footer); + ui.content = content; + } + if (content) { + // remove child if content exist and value is set to false + if (value === false) { + element.removeChild(content); + ui.content = null; + } else { + // if options is set to true, to string or not is set + // add class + content.classList.add(classes.uiContent); + // if is string fill content by string value + if (typeof value === "string") { + content.textContent = value; + } + } + // and remember options + self.options.content = value; + } + }; + + /** + * Method creates empty page header. It also checks for additional + * content to be added in header. + * @method _buildHeader + * @param {HTMLElement} element + * @protected + * @member ns.widget.core.Page + */ + prototype._buildHeader = function (element) { + var self = this; + + self._ui.header = utilSelectors.getChildrenBySelector(element, HEADER_SELECTOR)[0] || null; + if (self.options.header === undefined) { + self.options.header = !!self._ui.header; + } + self._setHeader(element, self.options.header); + }; + + /** + * Method creates empty page footer. + * @method _buildFooter + * @param {HTMLElement} element + * @protected + * @member ns.widget.core.Page + */ + prototype._buildFooter = function (element) { + var self = this; + + self._ui.footer = utilSelectors.getChildrenBySelector(element, FOOTER_SELECTOR)[0] || null; + if (self.options.footer === undefined) { + self.options.footer = !!self._ui.footer; + } + self._setFooter(element, self.options.footer); + }; + + /** + * Method creates empty page content. + * @method _buildContent + * @param {HTMLElement} element + * @protected + * @member ns.widget.core.Page + */ + prototype._buildContent = function (element) { + var self = this; + + self._ui.content = utilSelectors.getChildrenBySelector(element, CONTENT_SELECTOR)[0] || null; + if (self.options.content === undefined) { + self.options.content = !!self._ui.content; + } + self._setContent(element, self.options.content); + }; + + + /** + * Set ARIA attributes on page structure + * @method _setAria + * @protected + * @member ns.widget.core.Page + */ + prototype._setAria = function () { + var self = this, + ui = self._ui, + content = ui.content, + header = ui.header, + footer = ui.footer, + title = ui.title; + + if (content) { + content.setAttribute("role", "main"); + } + + if (header) { + header.setAttribute("role", "header"); + } + + if (footer) { + footer.setAttribute("role", "footer"); + } + + if (title) { + title.setAttribute("role", "heading"); + title.setAttribute("aria-level", 1); + title.setAttribute("aria-label", "title"); + } + }; + + /** + * Find title of page + * @param {HTMLElement} element + * @method _setTitle + * @protected + * @member ns.widget.core.Page + */ + prototype._setTitle = function (element) { + var self = this, + dataPageTitle = utilsDOM.getNSData(element, "title"), + header = self._ui.header, + pageTitle = dataPageTitle, + titleElement; + + if (header) { + titleElement = utilSelectors.getChildrenBySelector(header, "h1, h2, h3, h4, h5, h6")[0]; + if (titleElement) { + titleElement.classList.add(classes.uiTitle); + } + + if (!pageTitle && titleElement) { + pageTitle = titleElement.innerText; + self._ui.title = titleElement; + } + + if (!dataPageTitle && pageTitle) { + utilsDOM.setNSData(element, "title", pageTitle); + } + } + }; + + /** + * Build page + * @method _build + * @param {HTMLElement} element + * @return {HTMLElement} + * @protected + * @member ns.widget.core.Page + */ + prototype._build = function (element) { + var self = this; + + element.classList.add(classes.uiPage); + self._buildHeader(element); + self._buildFooter(element); + self._buildContent(element); + self._setTitle(element); + self._setAria(); + + //it means that we are in wearable profile and we want to make a scrollview on page element (not content) + if (self.options.enablePageScroll === true && !element.querySelector("." + classes.uiScroller)) { + engine.instanceWidget(element, "Scrollview"); + } + return element; + }; + + + /** + * This method sets page active or inactive. + * + * @example + *
+ * + * + * @method setActive + * @param {boolean} [value=true] If true, then page will be active. Otherwise, page will be inactive. + * @member ns.widget.core.Page + */ + prototype.setActive = function (value) { + var elementClassList = this.element.classList; + + if (value || value === undefined) { + this.focus(); + elementClassList.add(classes.uiPageActive); + } else { + this.blur(); + elementClassList.remove(classes.uiPageActive); + } + }; + + /** + * Return current status of page. + * @method isActive + * @member ns.widget.core.Page + * @instance + */ + prototype.isActive = function () { + return this.element.classList.contains(classes.uiPageActive); + }; + + /** + * Sets the focus to page + * @method focus + * @member ns.widget.core.Page + */ + prototype.focus = function () { + var element = this.element, + focusable = element.querySelector("[autofocus]") || element; + + focusable.focus(); + }; + + /** + * Removes focus from page and all descendants + * @method blur + * @member ns.widget.core.Page + */ + prototype.blur = function () { + var element = this.element, + focusable = document.activeElement || element; + + focusable.blur(); + }; + + /** + * Bind events to widget + * @method _bindEvents + * @protected + * @member ns.widget.core.Page + */ + prototype._bindEvents = function () { + var self = this; + + self._contentFillAfterResizeCallback = self._contentFill.bind(self); + window.addEventListener("resize", self._contentFillAfterResizeCallback, false); + }; + + /** + * Refresh widget structure + * @method _refresh + * @protected + * @member ns.widget.core.Page + */ + prototype._refresh = function () { + this._restoreContentStyle(); + this._contentFill(); + }; + + /** + * Layouting page structure + * @method layout + * @internal + * @member ns.widget.core.Page + */ + prototype.layout = function () { + this._storeContentStyle(); + this._contentFill(); + }; + + /** + * This method triggers BEFORE_SHOW event. + * @method onBeforeShow + * @internal + * @member ns.widget.core.Page + */ + prototype.onBeforeShow = function () { + this.trigger(EventType.BEFORE_SHOW); + }; + + /** + * This method triggers SHOW event. + * @method onShow + * @internal + * @member ns.widget.core.Page + */ + prototype.onShow = function () { + this.trigger(EventType.SHOW); + }; + + /** + * This method triggers BEFORE_HIDE event. + * @method onBeforeHide + * @internal + * @member ns.widget.core.Page + */ + prototype.onBeforeHide = function () { + this.trigger(EventType.BEFORE_HIDE); + }; + + /** + * This method triggers HIDE event. + * @method onHide + * @internal + * @member ns.widget.core.Page + */ + prototype.onHide = function () { + this._restoreContentStyle(); + this.trigger(EventType.HIDE); + }; + + /** + * Destroy widget + * @method _destroy + * @param {HTMLElement} element + * @protected + * @member ns.widget.core.Page + */ + prototype._destroy = function (element) { + var self = this; + + element = element || self.element; + + window.removeEventListener("resize", self._contentFillAfterResizeCallback, false); + // destroy widgets on children + engine.destroyAllWidgets(element, true); + + self._contentFillAfterResizeCallback = null; + }; + + /** + * Return scroller + * @method getScroller + * @member ns.widget.core.Page + */ + prototype.getScroller = function () { + var element = this.element, + scroller = element.querySelector("." + classes.uiScroller); + + return scroller || element.querySelector("." + classes.uiContent) || element; + }; + + Page.prototype = prototype; + + Page.createEmptyElement = function () { + var div = document.createElement("div"); + + div.classList.add(classes.uiPage); + return div; + }; + + engine.defineWidget( + "Page", + "[data-role=page],.ui-page", + [ + "focus", + "blur", + "setActive" + ], + Page, + // for register in jQuery Mobile space + "mobile" + ); + + ns.widget.core.Page = Page; + }(window.document, ns)); +/** + * #Footer + * + * ## Creating footer + * + * @example template + *
+ * + * @class ns.widget.core.Footer + * @component-selector .ui-page > footer, .ui-page .ui-footer + * @component-type layout-component + * @extends ns.widget.BaseWidget + * + */ + +/** + * #Header + * + * @example template + *

Header

+ * + * @class ns.widget.core.Header + * @component-selector .ui-page > header, .ui-page > .ui-header + * @component-type layout-component + * @extends ns.widget.BaseWidget + */ +/** + * Button for menu with icon in header + * @style ui-more + * @selector .ui-btn + * @member ns.widget.core.Header + * @wearable + * @since 2.3.1 + */ +/** + * Icon style for menu button + * @style ui-icon-detail + * @selector .ui-btn.ui-more + * @member ns.widget.core.Header + * @wearable + * @since 2.3.1 + */ +/** + * Icon style for menu button + * @style ui-icon-overflow + * @selector .ui-btn.ui-more + * @member ns.widget.core.Header + * @wearable + * @since 2.3.1 + */ +/** + * Icon style for menu button + * @style ui-icon-selectall + * @selector .ui-btn.ui-more + * @member ns.widget.core.Header + * @wearable + * @since 2.3.1 + */ + +/** + * #Content + * + * @example template + *
+ * + * @class ns.widget.core.Content + * @component-selector .ui-content + * @component-type container-component + * @component-constraint 'bottom-button', 'checkbox', 'listview', 'processing', 'closet-tau-circle-progress', 'radio', 'toggleswitch', 'text', 'closet-image', 'sectionchanger' + * @extends ns.widget.BaseWidget + */ +/** + * Defines the buttons inside column width as 100% of the screen + * @style ui-grid-col-1 + * @selector div:not(.ui-grid-col-1):not(.ui-grid-col-2):not(.ui-grid-col-3):not(.ui-grid-row) + * @member ns.widget.core.Content + * @wearable + * @since 2.3.1 + */ +/** + * Defines the buttons inside column width as 50% of the screen + * @style ui-grid-col-2 + * @selector div:not(.ui-grid-col-1):not(.ui-grid-col-2):not(.ui-grid-col-3):not(.ui-grid-row) + * @member ns.widget.core.Content + * @wearable + * @since 2.3.1 + */ +/** + * Defines the buttons inside column width as 50% of the screen + * @style ui-grid-col-3 + * @selector div:not(.ui-grid-col-1):not(.ui-grid-col-2):not(.ui-grid-col-3):not(.ui-grid-row) + * @member ns.widget.core.Content + * @wearable + * @since 2.3.1 + */ +/** + * Arranges the buttons inside in a row + * @style ui-grid-row + * @selector div:not(.ui-grid-col-1):not(.ui-grid-col-2):not(.ui-grid-col-3):not(.ui-grid-row) + * @member ns.widget.core.Content + * @wearable + * @since 2.3.1 + */ +; +/*global window, ns, define */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/*jslint nomen: true, plusplus: true */ +/** + * # PageContainer Widget + * PageContainer is a widget, which is supposed to have multiple child pages but display only one at a time. + * + * @class ns.widget.core.PageContainer + * @extends ns.widget.BaseWidget + * @author Maciej Urbanski + * @author Piotr Karny + * @author Krzysztof Głodowski + */ +(function (document, ns) { + "use strict"; + var BaseWidget = ns.widget.BaseWidget, + util = ns.util, + DOM = util.DOM, + engine = ns.engine, + classes = { + pageContainer: "ui-page-container", + uiViewportTransitioning: "ui-viewport-transitioning", + out: "out", + in: "in", + reverse: "reverse", + uiPreIn: "ui-pre-in", + uiBuild: "ui-page-build" + }, + PageContainer = function () { + /** + * Active page. + * @property {ns.widget.core.Page} [activePage] + * @member ns.widget.core.PageContainer + */ + this.activePage = null; + this.inTransition = false; + }, + EventType = { + /** + * Triggered before the changePage() request + * has started loading the page into the DOM. + * @event pagebeforechange + * @member ns.widget.core.PageContainer + */ + PAGE_BEFORE_CHANGE: "pagebeforechange", + /** + * Triggered after the changePage() request + * has finished loading the page into the DOM and + * all page transition animations have completed. + * @event pagechange + * @member ns.widget.core.PageContainer + */ + PAGE_CHANGE: "pagechange", + PAGE_REMOVE: "pageremove" + }, + animationend = "animationend", + webkitAnimationEnd = "webkitAnimationEnd", + mozAnimationEnd = "mozAnimationEnd", + msAnimationEnd = "msAnimationEnd", + oAnimationEnd = "oAnimationEnd", + animationEndNames = [ + animationend, + webkitAnimationEnd, + mozAnimationEnd, + msAnimationEnd, + oAnimationEnd + ], + prototype = new BaseWidget(); + //When resolved deferred function is responsible for triggering events related to page change as well as + //destroying unused widgets from last page and/or removing last page + + function deferredFunction(fromPageWidget, toPageWidget, self, options) { + if (fromPageWidget) { + fromPageWidget.onHide(); + if (options.reverse) { + fromPageWidget.destroy(); + } + self._removeExternalPage(fromPageWidget, options); + } + toPageWidget.onShow(); + self.trigger(EventType.PAGE_CHANGE); + } + + /** + * Dictionary for PageContainer related event types. + * @property {Object} events + * @property {string} [events.PAGE_CHANGE="pagechange"] + * @member ns.router.route.popup + * @static + */ + PageContainer.events = EventType; + + /** + * Dictionary for PageContainer related css class names + * @property {Object} classes + * @member ns.widget.core.Page + * @static + * @readonly + */ + PageContainer.classes = classes; + + /** + * Build widget structure + * @method _build + * @param {HTMLElement} element + * @return {HTMLElement} + * @member ns.widget.core.PageContainer + * @protected + */ + prototype._build = function (element) { + element.classList.add(classes.pageContainer); + return element; + }; + + /** + * This method changes active page to specified element. + * @method change + * @param {HTMLElement} toPageElement The element to set + * @param {Object} [options] Additional options for the transition + * @param {string} [options.transition=none] Specifies the type of transition + * @param {boolean} [options.reverse=false] Specifies the direction of transition + * @member ns.widget.core.PageContainer + */ + prototype.change = function (toPageElement, options) { + var self = this, + fromPageWidget = self.getActivePage(), + toPageWidget, + calculatedOptions = options || {}; + + // store options to detect that option was changed before process finish + self._options = calculatedOptions; + + calculatedOptions.widget = calculatedOptions.widget || "Page"; + + // The change should be made only if no active page exists + // or active page is changed to another one. + if (!fromPageWidget || (fromPageWidget.element !== toPageElement)) { + if (toPageElement.parentNode !== self.element) { + toPageElement = self._include(toPageElement); + } + + self.trigger(EventType.PAGE_BEFORE_CHANGE); + + toPageElement.classList.add(classes.uiBuild); + + toPageWidget = engine.instanceWidget(toPageElement, calculatedOptions.widget); + + // set sizes of page for correct display + toPageWidget.layout(); + + if (toPageWidget.option("autoBuildWidgets")) { + engine.createWidgets(toPageElement); + } + + if (fromPageWidget) { + fromPageWidget.onBeforeHide(); + } + toPageWidget.onBeforeShow(); + + toPageElement.classList.remove(classes.uiBuild); + + // if options is different that this mean that another change page was called and we need stop + // previous change page + if (calculatedOptions === self._options) { + calculatedOptions.deferred = { + resolve: deferredFunction + }; + self._transition(toPageWidget, fromPageWidget, calculatedOptions); + } + } + }; + + /** + * This method performs transition between the old and a new page. + * @method _transition + * @param {ns.widget.core.Page} toPageWidget The new page + * @param {ns.widget.core.Page} fromPageWidget The page to be replaced + * @param {Object} [options] Additional options for the transition + * @param {string} [options.transition=none] The type of transition + * @param {boolean} [options.reverse=false] Specifies transition direction + * @param {Object} [options.deferred] Deferred object + * @member ns.widget.core.PageContainer + * @protected + */ + prototype._transition = function (toPageWidget, fromPageWidget, options) { + var self = this, + element = self.element, + elementClassList = element.classList, + transition = !fromPageWidget || !options.transition ? "none" : options.transition, + deferred = options.deferred, + clearClasses = [classes.in, classes.out, classes.uiPreIn, transition], + oldDeferredResolve, + oneEvent; + + if (options.reverse) { + clearClasses.push(classes.reverse); + } + self.inTransition = true; + elementClassList.add(classes.uiViewportTransitioning); + oldDeferredResolve = deferred.resolve; + deferred.resolve = function () { + var fromPageWidgetClassList = fromPageWidget && fromPageWidget.element.classList, + toPageWidgetClassList = toPageWidget.element.classList; + + self._setActivePage(toPageWidget); + self._clearTransitionClasses(clearClasses, fromPageWidgetClassList, toPageWidgetClassList); + oldDeferredResolve(fromPageWidget, toPageWidget, self, options); + }; + + if (transition !== "none") { + oneEvent = function () { + toPageWidget.off( + animationEndNames, + oneEvent, + false + ); + deferred.resolve(); + }; + toPageWidget.on( + animationEndNames, + oneEvent, + false + ); + self._appendTransitionClasses(fromPageWidget, toPageWidget, transition, options.reverse); + } else { + window.setTimeout(deferred.resolve, 0); + } + }; + + /** + * This method adds proper transition classes to specified page widgets. + * @param {ns.widget.core.Page} fromPageWidget Page widget from which transition will occur + * @param {ns.widget.core.Page} toPageWidget Destination page widget for transition + * @param {string} transition Specifies the type of transition + * @param {boolean} reverse Specifies the direction of transition + * @member ns.widget.core.PageContainer + * @protected + */ + prototype._appendTransitionClasses = function (fromPageWidget, toPageWidget, transition, reverse) { + var classList; + + if (fromPageWidget) { + classList = fromPageWidget.element.classList; + classList.add(transition, classes.out); + if (reverse) { + classList.add(classes.reverse); + } + } + + classList = toPageWidget.element.classList; + classList.add(transition, classes.in, classes.uiPreIn); + if (reverse) { + classList.add(classes.reverse); + } + }; + + /** + * This method removes transition classes from classLists of page widget elements. + * @param {Object} clearClasses An array containing classes to be removed + * @param {Object} fromPageWidgetClassList classList object from source page element + * @param {Object} toPageWidgetClassList classList object from destination page element + * @member ns.widget.core.PageContainer + * @protected + */ + prototype._clearTransitionClasses = function (clearClasses, fromPageWidgetClassList, toPageWidgetClassList) { + var self = this, + element = self.element, + elementClassList = element.classList; + + elementClassList.remove(classes.uiViewportTransitioning); + self.inTransition = false; + clearClasses.forEach(function (className) { + toPageWidgetClassList.remove(className); + }); + if (fromPageWidgetClassList) { + clearClasses.forEach(function (className) { + fromPageWidgetClassList.remove(className); + }); + } + }; + + /** + * This method adds an element as a page. + * @method _include + * @param {HTMLElement} page an element to add + * @return {HTMLElement} + * @member ns.widget.core.PageContainer + * @protected + */ + prototype._include = function (page) { + var element = this.element; + + if (!page.parentNode || page.ownerDocument !== document) { + page = util.importEvaluateAndAppendElement(page, element); + } + return page; + }; + + /** + * This method sets currently active page. + * @method _setActivePage + * @param {ns.widget.core.Page} page a widget to set as the active page + * @member ns.widget.core.PageContainer + * @protected + */ + prototype._setActivePage = function (page) { + var self = this; + + if (self.activePage) { + self.activePage.setActive(false); + } + + self.activePage = page; + + page.setActive(true); + }; + + /** + * This method returns active page widget. + * @method getActivePage + * @member ns.widget.core.PageContainer + * @return {ns.widget.core.Page} Currently active page + */ + prototype.getActivePage = function () { + return this.activePage; + }; + + /** + * This method removes page element from the given widget and destroys it. + * @method _removeExternalPage + * @param {ns.widget.core.Page} fromPageWidget the widget to destroy + * @param {Object} [options] transition options + * @param {boolean} [options.reverse=false] specifies transition direction + * @member ns.widget.core.PageContainer + * @protected + */ + prototype._removeExternalPage = function (fromPageWidget, options) { + var fromPageElement = fromPageWidget.element; + + if (options && options.reverse && DOM.hasNSData(fromPageElement, "external") && + fromPageElement.parentNode) { + fromPageElement.parentNode.removeChild(fromPageElement); + this.trigger(EventType.PAGE_REMOVE); + } + }; + + PageContainer.prototype = prototype; + + // definition + ns.widget.core.PageContainer = PageContainer; + + engine.defineWidget( + "pagecontainer", + "", + ["change", "getActivePage"], + PageContainer, + "core" + ); + }(window.document, ns)); + +/*global define, ns */ +/*jslint nomen: true, plusplus: true, bitwise: false */ +/* + * Copyright (c) 2010 - 2014 Samsung Electronics Co., Ltd. + * License : MIT License V2 + */ +/** + * #Template Manager + * + * Object menage template's engines and renderer HTMLElement by template engine. + * + * @class ns.template + * @since 2.4 + * @author Maciej Urbanski + * @author Jadwiga Sosnowska + * @author Krzysztof Antoszek + */ +(function () { + "use strict"; + var utilPath = ns.util.path, + template, + templateFunctions = {}, + globalOptions = { + "pathPrefix": "", + "default": "" + }; + + /** + * Function to get global option + * + * @example + * tau.template.get("pathPrefix"); + * // -> "/prefix/to/all/paths" + * + * @method get + * @param {string} name param name which will be return + * @return {*} return value of option + * @since 2.4 + * @member ns.template + */ + function get(name) { + return globalOptions[name]; + } + + /** + * Function to set global option + * + * @example + * tau.template.set("pathPrefix", "/views"); + * + * @method set + * @param {string} name param name which will be set + * @param {*} value value to set + * @since 2.4 + * @member ns.template + */ + function set(name, value) { + globalOptions[name] = value; + } + + /** + * Register new template function + * + * Template function should have 4 arguments: + * + * - globalOptions - global options of template engine + * - path - path or id of template content + * - data - data for template render + * - callback - callback call on finish + * + * and should call callback on finish with arguments: + * + * - status - object describing status of render + * - element - base HTMLElement of template results (on error can be null) + * + * after registration you can use engine in render function. + * + * @example + * tau.template.register("inline", function(globalOptions, path, data, callback) { + * callback({ + * success: true + * }, + * document.createElement("div") + * ); + * }); + * + * @method register + * @param {string} name Engine name + * @param {Function} templateFunction function to renderer template + * @since 2.4 + * @member ns.template + */ + function register(name, templateFunction) { + templateFunctions[name] = templateFunction; + } + + /** + * Unregister template function + * + * @method unregister + * @param {string} name Engine name + * @since 2.4 + * @member ns.template + */ + function unregister(name) { + templateFunctions[name] = null; + } + + /** + * Return engine with given name + * + * @method engine + * @param {string} name Engine name + * @since 2.4 + * @member ns.template + */ + function engine(name) { + return templateFunctions[name]; + } + + /** + * Create absolute path for given path. + * If parameter withProfile is true, the returned path will have name of profile + * separated by dots before the last dot. + * @method getAbsUrl + * @param {string} path + * @param {boolean} withProfile Create path with profile's name + * @return {string} changed path + * @since 2.4 + */ + function getAbsUrl(path, withProfile) { + var profile = ns.info.profile, + lastDot = path.lastIndexOf("."); + + if (utilPath.isAbsoluteUrl(path)) { + return path; + } + + if (withProfile) { + path = path.substring(0, lastDot) + "." + profile + path.substring(lastDot); + } + + return utilPath.makeUrlAbsolute((globalOptions.pathPrefix || "") + path, utilPath.getLocation()); + } + + /** + * Return HTMLElement for given path + * + * When engine name is not given then get default name from global options. If this is not set then get first registered engine. + * + * Result of this method is handed to callback. First parameter of callback is object with status. Second is HTMLElement generated by engine. + * + * Status object contains properties: + * + * - _boolean_ success - inform about success or error + * - _string_ description contains details on error + * + * @example + * tau.template.render("external/path/to/file.html", {additionalParameter: true}, function(status, element) { + * if (status.success) { + * document.body.appendChild(element); + * } else { + * console.error(status.description); + * }, "html"); + * + * @method render + * @param {string} path Path to file ot other id for template system + * @param {Object} data additional data for template system + * @param {Function} callback function which will be called on finish + * @param {string} [engineName] engine name + * @since 2.4 + * @member ns.template + */ + function render(path, data, callback, engineName) { + var templateFunction = templateFunctions[engineName || get("default") || ""], + targetCallback = function (status, element) { + // add current patch + status.absUrl = targetPath; + callback(status, element); + }, + templateCallback = function (status, element) { + if (status.success) { + // path was found and callback can be called + targetCallback(status, element); + } else { + // try one more time with path without profile + targetPath = getAbsUrl(path, false); + templateFunction(globalOptions, targetPath, data || {}, targetCallback); + } + }, + targetPath; + + // if template engine name and default name is not given then we + // take first registered engine + if (!templateFunction) { + templateFunction = templateFunctions[Object.keys(templateFunctions).pop()]; + } + + // if template system exists then we go to him + if (templateFunction) { + targetPath = getAbsUrl(path, ns.getConfig("findProfileFile", false)); + templateFunction(globalOptions, targetPath, data || {}, templateCallback); + } else { + // else we return error + callback({ + success: false, + description: "Can't get engine system" + }, null); + } + } + + template = { + get: get, + set: set, + register: register, + unregister: unregister, + engine: engine, + render: render + }; + + ns.template = template; + }()); + +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* global define, HTMLElement, ns */ +/** + * #Router + * + * Main class to navigate between pages, popups and other widgets which has own rules in all profiles. + * + * Class communicates with PageContainer which deactivate and activate changed pages. + * + * Router listening on events triggered by history manager. + * + * ## Getting instance + * + * To receive instance of router you should use method _getInstance_ + * + * @example + * var router = ns.router.Router.getInstance(); + * + * By default TAU create instance of router and getInstance method return this instance. + * + * ##Connected widgets + * + * Router cooperate with widgets: + * + * - Page + * - Popup + * - Drawer + * - Dialog (mobile) + * - CircularIndexScrollBar (wearable - circle) + * + * Opening or closing these widgets are possible by create link with correct rel. + * + * ##Global options used in router + * + * - *pageContainer* = document.body - default container element + * - *pageContainerBody* = false - use body instead pageContainer option + * - *autoInitializePage* = true - automatically initialize first page + * - *addPageIfNotExist* = true - automatically add page if doesn't exist + * - *loader* = false - enable loader on change page + * - *disableRouter* = false - disable auto initialize of router + * + * @class ns.router.Router + * @author Maciej Urbanski + * @author Piotr Karny + * @author Tomasz Lukawski + * @author Hyunkook, Cho + * @author Piotr Czajka + * @author Junhyeon Lee + * @author Michał Szepielak + * @author Jadwiga Sosnowska + * @author Heeju Joo + */ +(function (window, document) { + "use strict"; + /** + * Local alias for ns.util + * @property {Object} util Alias for {@link ns.util} + * @member ns.router.Router + * @static + * @private + */ + var util = ns.util, + /** + * Local alias for ns.event + * @property {Object} eventUtils Alias for {@link ns.event} + * @member ns.router.Router + * @static + * @private + */ + eventUtils = ns.event, + /** + * Alias for {@link ns.util.DOM} + * @property {Object} DOM + * @member ns.router.Router + * @static + * @private + */ + DOM = util.DOM, + /** + * Local alias for ns.util.path + * @property {Object} path Alias for {@link ns.util.path} + * @member ns.router.Router + * @static + * @private + */ + path = util.path, + /** + * Local alias for ns.util.selectors + * @property {Object} selectors Alias for {@link ns.util.selectors} + * @member ns.router.Router + * @static + * @private + */ + selectors = util.selectors, + /** + * Local alias for ns.util.object + * @property {Object} object Alias for {@link ns.util.object} + * @member ns.router.Router + * @static + * @private + */ + object = util.object, + /** + * Local alias for ns.engine + * @property {Object} engine Alias for {@link ns.engine} + * @member ns.router.Router + * @static + * @private + */ + engine = ns.engine, + /** + * Local alias for ns.router + * @property {Object} router Alias for namespace ns.router + * @member ns.router.Router + * @static + * @private + */ + router = ns.router, + /** + * Local alias for ns.history + * @property {Object} history Alias for {@link ns.history} + * @member ns.router.Router + * @static + * @private + */ + history = ns.history, + historyManager = history.manager, + /** + * Local alias for ns.history.manager.events + * @property {Object} historyManagerEvents Alias for (@link ns.history.manager.events} + * @member ns.router.Router + * @static + * @private + */ + historyManagerEvents = historyManager.events, + /** + * Local alias for ns.router.route + * @property {Object} route Alias for namespace ns.router.route + * @member ns.router.Router + * @static + * @private + */ + route = router.route, + /** + * Local alias for document body element + * @property {HTMLElement} body + * @member ns.router.Router + * @static + * @private + */ + body = document.body, + /** + * Alias to Array.slice method + * @method slice + * @member ns.router.Router + * @private + * @static + */ + slice = [].slice, + /** + * Local instance of the Router + * @property {Object} routerInstance + * @member ns.router.Router + * @static + * @private + */ + _isLock = false, + + ORDER_NUMBER = { + 1: "page", + 10: "panel", + 100: "popup", + 101: "dialog", + 1000: "drawer", + 2000: "circularindexscrollbar" + }, + + eventType = { + BEFORE_ROUTER_INIT: "beforerouterinit", + ROUTER_INIT: "routerinit" + }, + + HASH_REGEXP = /[#|\s]/g, + + Page = ns.widget.core.Page, + + routerInstance, + + template = ns.template, + + Router = function () { + var self = this; + + /** + * Instance of widget PageContainer which controls page changing. + * @property {?ns.widget.core.PageContainer} [container=null] + * @member ns.router.Router + */ + self.container = null; + /** + * Settings for last call of method open + * @property {Object} [settings={}] + * @member ns.router.Router + */ + self.settings = {}; + + /** + * Handler for event "statechange" + * @property {Function} [_onStateChangeHandler=null] + * @member ns.router.Router + * @protected + * @since 2.4 + */ + self._onStateChangeHandler = null; + /** + * Handler for event "hashchange" + * @property {Function} [_onHashChangeHandler=null] + * @member ns.router.Router + * @protected + * @since 2.4 + */ + self._onHashChangeHandler = null; + /** + * Handler for event "controllercontent" + * @property {Function} [_onControllerContent=null] + * @member ns.router.Router + * @protected + * @since 2.4 + */ + self._onControllerContent = null; + + /** + * Router locking flag + * @property {boolean} locked=false + * @member ns.router.Router + * @since 2.4 + */ + self.locked = false; + }; + + /** + * Default values for router + * @property {Object} defaults + * @property {boolean} [defaults.fromHashChange=false] Sets if will be changed after hashchange. + * @property {boolean} [defaults.reverse=false] Sets the direction of change. + * @property {boolean} [defaults.volatileRecord=false] Sets if the current history entry will be modified or a new one will be created. + * @member ns.router.Router + */ + Router.prototype.defaults = { + fromHashChange: false, + reverse: false, + volatileRecord: false + }; + + /** + * Find the closest link for element + * @method findClosestLink + * @param {HTMLElement} element + * @return {HTMLElement} + * @private + * @static + * @member ns.router.Router + */ + function findClosestLink(element) { + while (element) { + if (element.nodeType === Node.ELEMENT_NODE && element.nodeName && element.nodeName === "A") { + break; + } + element = element.parentNode; + } + return element; + } + + /** + * Handle event link click + * @method linkClickHandler + * @param {ns.router.Router} router + * @param {Event} event + * @private + * @static + * @member ns.router.Router + */ + function linkClickHandler(router, event) { + var link = findClosestLink(event.target), + href, + useDefaultUrlHandling, + options; + + if (link && event.which === 1) { + href = link.getAttribute("href"); + useDefaultUrlHandling = (link.getAttribute("rel") === "external") || link.hasAttribute("target"); + if (!useDefaultUrlHandling) { + options = DOM.getData(link); + router.open(href, options, event); + eventUtils.preventDefault(event); + } + } + } + + Router.prototype.linkClick = function (event) { + linkClickHandler(this, event); + }; + + function openUrlFromState(instanceRouter, state) { + var rules = router.route, + prevState = history.activeState, + reverse = state && history.getDirection(state) === "back", + maxOrderNumber, + orderNumberArray = [], + ruleKey, + options, + url = path.getLocation(), + isContinue = true, + transition, + rule; + + transition = reverse ? ((prevState && prevState.transition) || "none") : state.transition; + options = object.merge({}, state, { + reverse: reverse, + transition: transition, + fromHashChange: true + }); + + // find rule with max order number + for (ruleKey in rules) { + if (rules.hasOwnProperty(ruleKey) && rules[ruleKey].active) { + orderNumberArray.push(rules[ruleKey].orderNumber); + } + } + maxOrderNumber = Math.max.apply(null, orderNumberArray); + rule = rules[ORDER_NUMBER[maxOrderNumber]]; + + if (rule && rule.onHashChange(url, options, prevState)) { + if (maxOrderNumber === 10) { + // rule is panel + return; + } + isContinue = false; + } + + history.setActive(state); + if (isContinue) { + instanceRouter.open(state.url, options); + } + } + + /** + * Detect rel attribute from HTMLElement. + * + * This method tries to match element to each rule filter and return first rule name which match. + * + * If don't match any rule then return null. + * + * @example + * var router = tau.router.Router.getInstance(); + * router.detectRel(document.getElementById("pageId")); + * // if HTML element will be match to selector of page then return rule for page + * + * @param {HTMLElement} to element to check + * @member ns.router.Router + * @return {?string} + */ + Router.prototype.detectRel = function (to) { + var rule, + i; + + for (i in route) { + if (route.hasOwnProperty(i)) { + rule = route[i]; + if (selectors.matchesSelector(to, rule.filter)) { + return i; + } + } + } + + return null; + }; + + + /** + * Open given page with deferred + * @method _openDeferred + * @param {HTMLElement} to HTMLElement of page + * @param {Object} [options] + * @param {"page"|"popup"|"external"} [options.rel = "page"] Represents kind of link as "page" + * or "popup" + * or "external" for linking to another domain. + * @param {string} [options.transition = "none"] Sets the animation used during change of + * page. + * @param {boolean} [options.reverse = false] Sets the direction of change. + * @param {boolean} [options.fromHashChange = false] Sets if will be changed after hashchange. + * @param {boolean} [options.volatileRecord = false] Sets if the current history entry will + * be modified or + * a new one will be created. + * @param {boolean} [options.dataUrl] Sets if page has url attribute. + * @param {?string} [options.container = null] It is used in RoutePopup as selector for + * container. + * @param {Event} event + * @member ns.router.Router + * @protected + */ + Router.prototype._openDeferred = function (to, options, event) { + var self = this, + rule = route[options.rel], + deferred = { + resolve: function (_options, content) { + rule.open(content, _options, event); + }, + reject: function (_options) { + eventUtils.trigger(self.container.element, "changefailed", _options); + } + }; + + if (typeof to === "string") { + if (to.replace(HASH_REGEXP, "")) { + self._loadUrl(to, options, rule, deferred); + } + } else { + // execute deferred object immediately + if (to && selectors.matchesSelector(to, rule.filter)) { + deferred.resolve(options, to); + } else { + deferred.reject(options); + } + } + }; + + /** + * Change page to page given in parameter "to". + * + * @example + * var router = tau.router.Router.getInstance(); + * router.open("pageId"); + * // open page with given id + * router.open("page.html"); + * // open page from html file + * router.open("popupId"); + * // open popup with given id + * + * @method open + * @param {string|HTMLElement} to Id of page or file url or HTMLElement of page + * @param {Object} [options] + * @param {"page"|"popup"|"external"} [options.rel="page"] Represents kind of link as "page" or "popup" or "external" for linking to another domain. + * @param {string} [options.transition="none"] Sets the animation used during change of page. + * @param {boolean} [options.reverse=false] Sets the direction of change. + * @param {boolean} [options.fromHashChange=false] Sets if will be changed after hashchange. + * @param {boolean} [options.volatileRecord=false] Sets if the current history entry will be modified or a new one will be created. + * @param {boolean} [options.dataUrl] Sets if page has url attribute. + * @param {?string} [options.container=null] It is used in RoutePopup as selector for container. + * @param {Event} [event] Event object + * @member ns.router.Router + */ + Router.prototype.open = function (to, options, event) { + var self = this, + rel, + rule; + + if (!_isLock) { + to = getHTMLElement(to); + rel = (options && options.rel) || + (to instanceof HTMLElement && self.detectRel(to)); + rel = rel || "page"; + rule = route[rel]; + + if (rel === "back") { + history.back(); + } else if (rule) { + options = object.merge( + { + rel: rel + }, + self.defaults, + rule.option(), + options + ); + self._openDeferred(to, options, event); + } else { + throw new Error("Not defined router rule [" + rel + "]"); + } + } + }; + + + /** + * Init routes defined in router + * @method _initRoutes + * @member ns.router.Router + */ + Router.prototype._initRoutes = function () { + var ruleKey, + rules = router.route; + + for (ruleKey in rules) { + if (rules.hasOwnProperty(ruleKey) && rules[ruleKey].init) { + rules[ruleKey].init(); + } + } + }; + + function removeActivePageClass(containerElement) { + var PageClasses = Page.classes, + uiPageActiveSelector = "." + PageClasses.uiPageActive, + activePages = slice.call(containerElement.querySelectorAll(uiPageActiveSelector)); + + activePages.forEach(function (page) { + page.classList.remove(uiPageActiveSelector); + }); + } + + Router.prototype._autoInitializePage = function (containerElement, pages, pageSelector) { + var self = this, + page, + location = window.location, + uiPageActiveClass = Page.classes.uiPageActive, + firstPage = containerElement.querySelector("." + uiPageActiveClass); + + if (!firstPage) { + firstPage = pages[0]; + } + + if (firstPage) { + removeActivePageClass(containerElement); + } + + if (location.hash) { + //simple check to determine if we should show firstPage or other + page = document.getElementById(location.hash.replace("#", "")); + if (page && selectors.matchesSelector(page, pageSelector)) { + firstPage = page; + } + } + + if (!firstPage && ns.getConfig("addPageIfNotExist", true)) { + firstPage = Page.createEmptyElement(); + while (containerElement.firstChild) { + firstPage.appendChild(containerElement.firstChild); + } + containerElement.appendChild(firstPage); + } + + if (self.justBuild) { + if (firstPage) { + self.register( + engine.instanceWidget(containerElement, "pagecontainer"), + firstPage + ); + } + } + + return firstPage; + }; + + /** + * Method initializes page container and builds the first page if flag autoInitializePage is + * set. + * @method init + * @param {boolean} justBuild + * @member ns.router.Router + */ + Router.prototype.init = function (justBuild) { + var containerElement, + firstPage, + pages, + pageDefinition = ns.engine.getWidgetDefinition("Page"), + pageSelector = pageDefinition.selector, + self = this; + + eventUtils.trigger(document, eventType.BEFORE_ROUTER_INIT, self, false); + + body = document.body; + self.justBuild = justBuild; + + containerElement = ns.getConfig("pageContainer") || body; + pages = slice.call(containerElement.querySelectorAll(pageSelector)); + + if (!ns.getConfig("pageContainerBody", false)) { + containerElement = pages.length ? pages[0].parentNode : containerElement; + } + + if (ns.getConfig("autoInitializePage", true)) { + firstPage = self._autoInitializePage(containerElement, pages, pageSelector); + if (justBuild) { + return; + } + } + + historyManager.enable(); + + // init router's routes + self._initRoutes(); + + self.register( + engine.instanceWidget(containerElement, "pagecontainer"), + firstPage + ); + + eventUtils.trigger(document, eventType.ROUTER_INIT, self, false); + }; + + /** + * Method removes all events listeners set by router. + * + * Also remove singleton instance of router; + * + * @example + * var router = tau.router.Router.getInstance(); + * router.destroy(); + * var router2 = tau.router.Router.getInstance(); + * // router !== router2 + * + * @method destroy + * @member ns.router.Router + */ + Router.prototype.destroy = function () { + var self = this; + + historyManager.disable(); + + window.removeEventListener("popstate", self.popStateHandler, false); + if (body) { + body.removeEventListener("pagebeforechange", self.pagebeforechangeHandler, false); + body.removeEventListener("vclick", self.linkClickHandler, false); + } + }; + + /** + * Method sets instance of PageContainer widget + * + * @example + * var router = tau.router.Router.getInstance(); + * router.setContainer(new ns.widget.PageContainer()); + * + * @method setContainer + * @param {ns.widget.core.PageContainer} container + * @member ns.router.Router + */ + Router.prototype.setContainer = function (container) { + this.container = container; + }; + + /** + * Method returns instance of PageContainer widget + * + * @example + * var router = tau.router.Router.getInstance(); + * containerWidget = router.getContainer(); + * + * @method getContainer + * @return {ns.widget.core.PageContainer} + * @member ns.router.Router + */ + Router.prototype.getContainer = function () { + return this.container; + }; + + /** + * Method returns ths first page HTMLElement + * @method getFirstPage + * @return {HTMLElement} + * @member ns.router.Router + */ + Router.prototype.getFirstPage = function () { + return this.getRoute("page").getFirstElement(); + }; + + + /** + * Callback for event "historyhashchange" which is triggered by history manager after hash is changed + * @param {ns.router.Router} router + * @param {Event} event + */ + function onHistoryHashChange(router, event) { + openUrlFromState(router, event.detail); + } + + /** + * Callback for event "historystatechange" which is triggered by history manager after hash is changed + * @param {ns.router.Router} router + * @param {Event} event + */ + function onHistoryStateChange(router, event) { + var options = event.detail, + // + url = options.reverse ? options.url : (options.href || options.url); + + delete options.event; + router.open(url, options); + // prevent current event + eventUtils.preventDefault(event); + eventUtils.stopImmediatePropagation(event); + } + + /** + * Convert HTML string to HTMLElement + * @param {string|HTMLElement} content + * @param {string} title + * @return {?HTMLElement} + */ + function convertToNode(content, title) { + var contentNode = null, + externalDocument = document.implementation.createHTMLDocument(title), + externalBody = externalDocument.body; + + if (content instanceof HTMLElement) { + // if content is HTMLElement just set to contentNode + contentNode = content; + } else { + // otherwise convert string to HTMLElement + try { + externalBody.insertAdjacentHTML("beforeend", content); + contentNode = externalBody.firstChild; + } catch (e) { + ns.error("Failed to inject element", e); + } + } + return contentNode; + } + + /** + * Set data-url on HTMLElement if not exists + * @param {HTMLElement} contentNode + * @param {string} url + */ + function setURLonElement(contentNode, url) { + if (url) { + if (contentNode instanceof HTMLElement && !DOM.hasNSData(contentNode, "url")) { + // if url is missing we need set data-url attribute for good finding by method open in router + url = url.replace(/^#/, ""); + DOM.setNSData(contentNode, "url", url); + } + } + } + + /** + * Callback for event "controller-content-available" which is triggered by controller after application handle hash change + * @param {ns.router.Router} router + * @param {Event} event + */ + function onControllerContent(router, event) { + var data = event.detail, + content = data.content, + options = data.options, + contentNode, + url = (options.href || options.url); + + // if controller give content + if (content) { + // convert to node if content is string + contentNode = convertToNode(content, options.title); + + // set data-url on node + setURLonElement(contentNode, url); + + // calling open method + router.open(contentNode, options); + + //prevent event + eventUtils.preventDefault(event); + } + } + + + /** + * Method registers page container and the first page. + * + * @example + * var router = tau.router.Router.getInstance(); + * router.register(new ns.widget.PageContainer(), document.getElementById("firstPage")); + * + * @method register + * @param {ns.widget.core.PageContainer} container + * @param {HTMLElement} firstPage + * @member ns.router.Router + */ + Router.prototype.register = function (container, firstPage) { + var self = this, + routePopup = this.getRoute("popup"); + + // sets instance of PageContainer widget + self.container = container; + + // sets first page HTMLElement + self.getRoute("page").setFirstElement(firstPage); + + eventUtils.trigger(document, "themeinit", self); + + // sets events handlers + if (!self._onHashChangeHandler) { + self._onHashChangeHandler = onHistoryHashChange.bind(null, self); + window.addEventListener(historyManagerEvents.HASHCHANGE, self._onHashChangeHandler, false); + } + if (!self._onStateChangeHandler) { + self._onStateChangeHandler = onHistoryStateChange.bind(null, self); + window.addEventListener(historyManagerEvents.STATECHANGE, self._onStateChangeHandler, false); + } + if (!self._onControllerContent) { + self._onControllerContent = onControllerContent.bind(null, self); + window.addEventListener("controller-content-available", self._onControllerContent, false); + } + + // if loader config is set then create loader widget + if (ns.getConfig("loader", false)) { + container.element.appendChild(self.getLoader().element); + } + + // set history support + history.enableVolatileMode(); + + // if first page exist open this page without transition + if (firstPage) { + self.open(firstPage, {transition: "none"}); + } + + if (routePopup) { + routePopup.setActive(null); + } + }; + + /** + * Convert string id to HTMLElement or return HTMLElement if is given + * @param {string|HTMLElement} idOrElement + * @return {HTMLElement|string} + */ + function getHTMLElement(idOrElement) { + var stringId, + toElement; + + // if given argument is string then + if (typeof idOrElement === "string") { + if (idOrElement[0] === "#") { + // trim first char if it is # + stringId = idOrElement.substr(1); + } else { + stringId = idOrElement; + } + // find element by id + toElement = document.getElementById(stringId); + + if (toElement) { + // is exists element by id then return it + idOrElement = toElement; + } + // otherwise return string + } + return idOrElement; + } + + /** + * Method close route element, eg page or popup. + * + * @example + * var router = tau.router.Router.getInstance(); + * router.close("popupId", {transition: "none"}); + * + * @method close + * @param {string|HTMLElement} to Id of page or file url or HTMLElement of page + * @param {Object} [options] + * @param {"page"|"popup"|"external"} [options.rel="page"] Represents kind of link as "page" or "popup" or "external" for linking to another domain + * @member ns.router.Router + */ + Router.prototype.close = function (to, options) { + var rel = "back", + closingWidget = getHTMLElement(to), + rule; + + if (options && options.rel) { + rel = options.rel; + } else if (closingWidget) { + rel = this.detectRel(closingWidget); + } + + rule = route[rel]; + + // if router is not locked + if (!this.locked) { + // if rel is back then call back method + if (rel === "back") { + history.back(); + } else { + // otherwise if rule exists + if (rule) { + // call close on rule + rule.close(closingWidget, options); + } else { + throw new Error("Not defined router rule [" + rel + "]"); + } + } + } + }; + + /** + * Method back to previous state. + * + * @example + * var router = tau.router.Router.getInstance(); + * router.back(); + * + * @method close + * @member ns.router.Router + */ + Router.prototype.back = function () { + + // if router is not locked + if (!this.locked) { + history.back(); + } + }; + + /** + * Method opens popup. + * + * @example + * var router = tau.router.Router.getInstance(); + * router.openPopup("popupId", {transition: "none"}); + * + * @method openPopup + * @param {HTMLElement|string} to Id or HTMLElement of popup. + * @param {Object} [options] + * @param {string} [options.transition="none"] Sets the animation used during change of page. + * @param {boolean} [options.reverse=false] Sets the direction of change. + * @param {boolean} [options.fromHashChange=false] Sets if will be changed after hashchange. + * @param {boolean} [options.volatileRecord=false] Sets if the current history entry will be modified or a new one will be created. + * @param {boolean} [options.dataUrl] Sets if page has url attribute. + * @param {?string} [options.container=null] It is used in RoutePopup as selector for container. + * @member ns.router.Router + */ + Router.prototype.openPopup = function (to, options) { + // call method open with overwrite rel option + this.open(to, object.fastMerge({rel: "popup"}, options)); + }; + + /** + * Method closes popup. + * + * @example + * var router = tau.router.Router.getInstance(); + * router.closePopup(); + * + * @method closePopup + * @param {Object} options + * @param {string=} [options.transition] + * @param {string=} [options.ext="in ui-pre-in"] options.ext + * @member ns.router.Router + */ + Router.prototype.closePopup = function (options) { + var popupRoute = this.getRoute("popup"); + + if (popupRoute) { + popupRoute.close(null, options); + } + }; + + /** + * Lock router + * @method lock + * @member ns.router.Router + */ + Router.prototype.lock = function () { + this.locked = true; + }; + + /** + * Unlock router and history manager + * @method unlock + * @member ns.router.Router + */ + Router.prototype.unlock = function () { + this.locked = false; + }; + + /** + * Load content from url. + * + * Method prepare url and call template function to load external file. + * + * If option showLoadMsg is ste to tru open loader widget before start loading. + * + * @method _loadUrl + * @param {string} url full URL to load + * @param {Object} options options for this and next methods in chain + * @param {boolean} [options.data] Sets if page has url attribute. + * @param {Object} rule rule which support given call + * @param {Object} deferred object with callbacks + * @param {Function} deferred.reject callback on error + * @param {Function} deferred.resolve callback on success + * @member ns.router.Router + * @protected + */ + Router.prototype._loadUrl = function (url, options, rule, deferred) { + var absUrl = path.makeUrlAbsolute(url, path.getLocation()), + content, + self = this, + data = options.data || {}; + + // check if content is loaded in current document + content = rule.find(absUrl); + + // if content doesn't find and url is embedded url + if (!content && path.isEmbedded(absUrl)) { + // reject + deferred.reject({}); + } else { + // If the content we are interested in is already in the DOM, + // and the caller did not indicate that we should force a + // reload of the file, we are done. Resolve the deferred so that + // users can bind to .done on the promise + if (content) { + // content was found and we resolve + deferred.resolve(object.fastMerge({absUrl: absUrl}, options), content); + } else { + + // Load the new content. + eventUtils.trigger(self.getContainer().element, options.rel + "beforeload"); + + // force return full document from template system + data.fullDocument = true; + // we put url, not the whole path to function render, + // because this path can be modified by template's module + template.render(url, data, function (status, element) { + // if template loaded successful + if (status.success) { + self._loadSuccess(status.absUrl, options, rule, deferred, element); + eventUtils.trigger(self.getContainer().element, options.rel + "load"); + } else { + self._loadError(status.absUrl, options, deferred); + } + }); + } + } + }; + + /** + * Error handler for loading content by AJAX + * @method _loadError + * @param {string} absUrl full URL to load + * @param {Object} options options for this and next methods in chain + * @param {boolean} [options.showLoadMsg=true] Sets if message will be shown during loading. + * @param {Object} deferred object with callbacks + * @param {Function} deferred.reject callback on error + * @member ns.router.Router + * @protected + */ + Router.prototype._loadError = function (absUrl, options, deferred) { + var detail = object.fastMerge({url: absUrl}, options), + self = this; + + ns.error("load error, file: ", absUrl); + + self.container.trigger("loadfailed", detail); + deferred.reject(detail); + }; + + // TODO it would be nice to split this up more but everything appears to be "one off" + // or require ordering such that other bits are sprinkled in between parts that + // could be abstracted out as a group + /** + * Success handler for loading content by AJAX + * @method _loadSuccess + * @param {string} absUrl full URL to load + * @param {Object} options options for this and next methods in chain + * @param {boolean} [options.showLoadMsg=true] Sets if message will be shown during loading. + * @param {Object} rule rule which support given call + * @param {Object} deferred object with callbacks + * @param {Function} deferred.reject callback on error + * @param {Function} deferred.resolve callback on success + * @param {string} html + * @member ns.router.Router + * @protected + */ + Router.prototype._loadSuccess = function (absUrl, options, rule, deferred, html) { + var detail = object.fastMerge({url: absUrl}, options), + // find element with given id in returned html + content = rule.parse(html, absUrl); + + if (content) { + deferred.resolve(detail, content); + } else { + deferred.reject(detail); + } + }; + + // TODO the first page should be a property set during _create using the logic + // that currently resides in init + /** + * Get initial content + * @method _getInitialContent + * @member ns.router.Router + * @return {HTMLElement} the first page + * @protected + */ + Router.prototype._getInitialContent = function () { + return this.getRoute("page").getFirstElement(); + }; + + /** + * Report an error loading + * @method _showError + * @param {string} absUrl + * @member ns.router.Router + * @protected + */ + Router.prototype._showError = function (absUrl) { + ns.error("load error, file: ", absUrl); + }; + + /** + * Returns Page or Popup widget + * @param {string} [routeName="page"] in default page or popup + * @method getActive + * @return {ns.widget.BaseWidget} + * @member ns.router.Router + */ + Router.prototype.getActive = function (routeName) { + var route = this.getRoute(routeName || "page"); + + return route && route.getActive(); + }; + + /** + * Returns true if element in given route is active. + * @param {string} [routeName="page"] in default page or popup + * @method hasActive + * @return {boolean} + * @member ns.router.Router + */ + Router.prototype.hasActive = function (routeName) { + var route = this.getRoute(routeName || "page"); + + return !!(route && route.hasActive()); + }; + + /** + * Returns true if any popup is active. + * + * @example + * var router = tau.router.Router.getInstance(), + * hasActivePopup = router.hasActivePopup(); + * // -> true | false + * + * @method hasActivePopup + * @return {boolean} + * @member ns.router.Router + */ + Router.prototype.hasActivePopup = function () { + return this.hasActive("popup"); + }; + + /** + * This function returns proper route. + * + * @example + * var router = tau.router.Router.getInstance(), + * route = router.getRoute("page"), + * // -> Object with pages support + * activePage = route.getActive(); + * // instance of Page widget + * + * @method getRoute + * @param {string} type Type of route + * @return {?ns.router.route.interface} + * @member ns.router.Router + */ + Router.prototype.getRoute = function (type) { + return route[type]; + }; + + /** + * Returns instance of loader widget. + * + * If loader not exist then is created on first element matched to selector + * or is created new element. + * + * @example + * var loader = router.getLoader(); + * // get or create loader + * loader.show(); + * // show loader + * + * @return {?ns.widget.mobile.Loader} + * @member ns.router.Page + * @method getLoader + */ + Router.prototype.getLoader = function () { + var loaderDefinition = engine.getWidgetDefinition("Loader"), + loaderSelector = loaderDefinition.selector, + loaderElement; + + if (loaderDefinition) { + loaderElement = document.querySelector(loaderSelector); + return engine.instanceWidget(loaderElement, "Loader"); + } + return null; + }; + + /** + * Creates a new instance of the router and returns it + * + * @example + * var router = Router.newInstance(); + * + * @method newInstance + * @member ns.router.Router + * @static + * @return {ns.router.Router} + * @since 2.4 + */ + Router.newInstance = function () { + return (routerInstance = new Router()); + }; + + /** + * Returns a instance of the router, creates a new if does not exist + * + * @example + * var router = tau.router.Router.getInstance(), + * // if router not exists create new instance and return + * router2 = tau.router.Router.getInstance(); + * // only return router from first step + * // router === router2 + * + * @method getInstance + * @member ns.router.Router + * @return {ns.router.Router} + * @since 2.4 + * @static + */ + Router.getInstance = function () { + if (!routerInstance) { + return this.newInstance(); + } + return routerInstance; + }; + + router.Router = Router; + + Router.eventType = eventType; + + /** + * Returns router instance + * @deprecated 2,4 + * @return {ns.router.Router} + */ + engine.getRouter = function () { //@TODO FIX HACK old API + //@TODO this is suppressed since the tests are unreadable + // tests need fixes + ns.warn("getRouter() method is deprecated! Use tau.router.Router.getInstance() instead"); + return Router.getInstance(); + }; + + if (!ns.getConfig("disableRouter", false)) { + document.addEventListener(engine.eventType.READY, function () { + Router.getInstance().init(); + }, false); + document.addEventListener(engine.eventType.DESTROY, function () { + Router.getInstance().destroy(); + }, false); + } + + //engine.initRouter(Router); + }(window, window.document)); + +/*global window, ns, define, ns */ +/*jslint nomen: true */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * #Route Page + * Support class for router to control changing pages. + * @class ns.router.route.page + * @author Maciej Urbanski + */ +(function (document) { + "use strict"; + var util = ns.util, + path = util.path, + DOM = util.DOM, + object = util.object, + utilSelector = util.selectors, + history = ns.history, + engine = ns.engine, + baseElement, + routePage = {}, + head; + + /** + * Tries to find a page element matching id and filter (selector). + * Adds data url attribute to found page, sets page = null when nothing found + * @method findPageAndSetDataUrl + * @param {string} dataUrl DataUrl of searching element + * @param {string} filter Query selector for searching page + * @return {?HTMLElement} + * @private + * @static + * @member ns.router.route.page + */ + function findPageAndSetDataUrl(dataUrl, filter) { + var id = path.stripQueryParams(dataUrl).replace("#", ""), + page = document.getElementById(id); + + if (page && utilSelector.matchesSelector(page, filter)) { + if (dataUrl === id) { + DOM.setNSData(page, "url", "#" + id); + } else { + DOM.setNSData(page, "url", dataUrl); + } + + } else { + // if we matched any element, but it doesn't match our filter + // reset page to null + page = null; + } + return page; + } + + routePage.orderNumber = 1; + /** + * Property containing default properties + * @property {Object} defaults + * @property {string} defaults.transition="none" + * @static + * @member ns.router.route.page + */ + routePage.defaults = { + transition: "none" + }; + + /** + * Property defining selector without spaces for filtering only page elements. + * @property {string} filter + * @member ns.router.route.page + * @static + */ + routePage.filter = engine.getWidgetDefinition("Page").selector.replace(/(\s*)/g, ""); + + /** + * Property contains first page element + * @property {?HTMLElement} firstPage + * @member ns.router.route.page + * @static + */ + routePage.firstPage = null; + + /** + * Returns default route options used inside Router. + * @method option + * @static + * @member ns.router.route.page + * @return {Object} default route options + */ + routePage.option = function () { + var defaults = object.merge({}, routePage.defaults); + + defaults.transition = ns.getConfig("pageTransition", defaults.transition); + return defaults; + }; + + routePage.init = function () { + var pages = [].slice.call(document.querySelectorAll(this.filter)); + + pages.forEach(function (page) { + if (!DOM.getNSData(page, "url")) { + DOM.setNSData(page, "url", + (page.id && "#" + page.id) || location.pathname + location.search); + } + }); + }; + + /** + * This method changes page. It sets history and opens page passed as a parameter. + * @method open + * @param {HTMLElement|string} toPage The page which will be opened. + * @param {Object} [options] + * @param {boolean} [options.fromHashChange] Sets if call was made on hash change. + * @param {string} [options.dataUrl] Sets if page has url attribute. + * @member ns.router.route.page + */ + routePage.open = function (toPage, options) { + var pageTitle = document.title, + url, + state; + + if (toPage === this.getFirstElement() && !options.dataUrl) { + url = path.documentUrl.hrefNoHash; + } else { + url = DOM.getNSData(toPage, "url"); + } + + // if no url is set, apply the address of chosen page to data-url attribute + // and use it as url, as this is needed for history state + if (!url && options.href) { + url = options.href; + DOM.setNSData(toPage, "url", url); + } + + pageTitle = DOM.getNSData(toPage, "title") || + utilSelector.getChildrenBySelector(toPage, ".ui-header > .ui-title").textContent || + pageTitle; + if (!DOM.getNSData(toPage, "title")) { + DOM.setNSData(toPage, "title", pageTitle); + } + + if (url && !options.fromHashChange) { + if (!path.isPath(url) && url.indexOf("#") < 0) { + url = path.makeUrlAbsolute("#" + url, path.documentUrl.hrefNoHash); + } + + state = object.merge( + {}, + options, + { + url: url + } + ); + + history.replace(state, pageTitle, url); + } + + // write base element + this._setBase(url); + + //set page title + document.title = pageTitle; + this.active = true; + this.getContainer().change(toPage, options); + + }; + + /** + * This method determines target page to open. + * @method find + * @param {string} absUrl Absolute path to opened page + * @member ns.router.route.page + * @return {?HTMLElement} Element of page to open. + */ + routePage.find = function (absUrl) { + var self = this, + router = ns.router.Router.getInstance(), + dataUrl = self._createDataUrl(absUrl), + initialContent = self.getFirstElement(), + pageContainer = router.getContainer(), + page, + selector = "[data-url='" + dataUrl + "']", + filterRegexp = /,/gm; + + if (/#/.test(absUrl) && path.isPath(dataUrl)) { + return null; + } + + // Check to see if the page already exists in the DOM. + // NOTE do _not_ use the :jqmData pseudo selector because parenthesis + // are a valid url char and it breaks on the first occurrence + // prepare selector for new page + selector += self.filter.replace(filterRegexp, ",[data-url='" + dataUrl + "']"); + page = pageContainer.element.querySelector(selector); + + // If we failed to find the page, check to see if the url is a + // reference to an embedded page. If so, it may have been dynamically + // injected by a developer, in which case it would be lacking a + // data-url attribute and in need of enhancement. + if (!page && dataUrl && !path.isPath(dataUrl)) { + //Remove search data + page = findPageAndSetDataUrl(dataUrl, self.filter); + } + + // If we failed to find a page in the DOM, check the URL to see if it + // refers to the first page in the application. Also check to make sure + // our cached-first-page is actually in the DOM. Some user deployed + // apps are pruning the first page from the DOM for various reasons. + // We check for this case here because we don't want a first-page with + // an id falling through to the non-existent embedded page error case. + if (!page && + path.isFirstPageUrl(dataUrl, self.getFirstElement()) && + initialContent) { + page = initialContent; + } + + return page; + }; + + /** + * This method parses HTML and runs scripts from parsed code. + * Fetched external scripts if required. + * Sets document base to parsed document absolute path. + * @method parse + * @param {string} html HTML code to parse + * @param {string} absUrl Absolute url for parsed page + * @member ns.router.route.page + * @return {?HTMLElement} Element of page in parsed document. + */ + routePage.parse = function (html, absUrl) { + var self = this, + page, + dataUrl = self._createDataUrl(absUrl); + + // write base element + self._setBase(absUrl); + + // Finding matching page inside created element + page = html.querySelector(self.filter); + + // If a page exists... + if (page) { + DOM.setNSData(page, "url", dataUrl); + DOM.setNSData(page, "external", true); + } + return page; + }; + + /** + * This method handles hash change, **currently does nothing**. + * @method onHashChange + * @static + * @member ns.router.route.page + * @return {null} + */ + routePage.onHashChange = function (/* url, options */) { + return null; + }; + + /** + * This method creates data url from absolute url given as argument. + * @method _createDataUrl + * @param {string} absoluteUrl + * @protected + * @static + * @member ns.router.route.page + * @return {string} + */ + routePage._createDataUrl = function (absoluteUrl) { + return path.convertUrlToDataUrl(absoluteUrl, true); + }; + + /** + * On open fail, currently never used + * @method onOpenFailed + * @member ns.router.route.page + */ + routePage.onOpenFailed = function (/* options */) { + this._setBase(path.parseLocation().hrefNoSearch); + }; + + /** + * This method returns base element from document head. + * If no base element is found, one is created based on current location. + * @method _getBaseElement + * @protected + * @static + * @member ns.router.route.page + * @return {HTMLElement} + */ + routePage._getBaseElement = function () { + // Fetch document head if never cached before + if (!head) { + head = document.querySelector("head"); + } + // Find base element + if (!baseElement) { + baseElement = document.querySelector("base"); + if (!baseElement) { + baseElement = document.createElement("base"); + baseElement.href = path.documentBase.hrefNoHash; + head.appendChild(baseElement); + } + } + return baseElement; + }; + + /** + * Sets document base to url given as argument + * @method _setBase + * @param {string} url + * @protected + * @member ns.router.route.page + */ + routePage._setBase = function (url) { + var base = this._getBaseElement(), + baseHref = base.href; + + if (path.isPath(url)) { + url = path.makeUrlAbsolute(url, path.documentBase); + if (path.parseUrl(baseHref).hrefNoSearch !== path.parseUrl(url).hrefNoSearch) { + base.href = url; + path.documentBase = path.parseUrl(path.makeUrlAbsolute(url, path.documentUrl.href)); + } + } + }; + + /** + * Returns container of pages + * @method getContainer + * @return {?ns.widget.core.Page} + * @member ns.router.route.page + * @static + */ + routePage.getContainer = function () { + return ns.router.Router.getInstance().getContainer(); + }; + + /** + * Returns active page. + * @method getActive + * @return {?ns.widget.core.Page} + * @member ns.router.route.page + * @static + */ + routePage.getActive = function () { + return this.getContainer().getActivePage(); + }; + + /** + * Returns element of active page. + * @method getActiveElement + * @return {HTMLElement} + * @member ns.router.route.page + * @static + */ + routePage.getActiveElement = function () { + return this.getActive().element; + }; + + /** + * Method returns ths first page. + * @method getFirstElement + * @return {HTMLElement} the first page + * @member ns.router.route.page + */ + routePage.getFirstElement = function () { + return this.firstPage; + }; + + /** + * Method sets ths first page. + * @method setFirstElement + * @param {HTMLElement} firstPage the first page + * @member ns.router.route.page + */ + routePage.setFirstElement = function (firstPage) { + this.firstPage = firstPage; + }; + + ns.router.route.page = routePage; + + }(window.document)); + +/*global window, ns, define, ns */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * #Callback Utility + * Class creates a callback list + * + * Create a callback list using the following parameters: + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * @class ns.util.callbacks + */ +(function (window, document, ns) { + "use strict"; + ns.util.callbacks = function (orgOptions) { + + var object = ns.util.object, + options = object.copy(orgOptions), + /** + * Alias to Array.slice function + * @method slice + * @member ns.util.callbacks + * @private + */ + slice = [].slice, + /** + * Last fire value (for non-forgettable lists) + * @property {Object} memory + * @member ns.util.callbacks + * @private + */ + memory, + /** + * Flag to know if list was already fired + * @property {boolean} fired + * @member ns.util.callbacks + * @private + */ + fired, + /** + * Flag to know if list is currently firing + * @property {boolean} firing + * @member ns.util.callbacks + * @private + */ + firing, + /** + * First callback to fire (used internally by add and fireWith) + * @property {number} [firingStart=0] + * @member ns.util.callbacks + * @private + */ + firingStart, + /** + * End of the loop when firing + * @property {number} firingLength + * @member ns.util.callbacks + * @private + */ + firingLength, + /** + * Index of currently firing callback (modified by remove if needed) + * @property {number} firingIndex + * @member ns.util.callbacks + * @private + */ + firingIndex, + /** + * Actual callback list + * @property {Array} list + * @member ns.util.callbacks + * @private + */ + list = [], + /** + * Stack of fire calls for repeatable lists + * @property {Array} stack + * @member ns.util.callbacks + * @private + */ + stack = !options.once && [], + fire, + add, + self = { + /** + * Add a callback or a collection of callbacks to the list + * @method add + * @return {ns.util.callbacks} self + * @member ns.util.callbacks + */ + add: function () { + var start; + + if (list) { + // First, we save the current length + start = list.length; + + add(arguments); + // Do we need to add the callbacks to the + // current firing batch? + if (firing) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away + } else if (memory) { + firingStart = start; + fire(memory); + } + } + return this; + }, + /** + * Remove a callback from the list + * @method remove + * @return {ns.util.callbacks} self + * @member ns.util.callbacks + */ + remove: function () { + if (list) { + slice.call(arguments).forEach(function (arg) { + var index = list.indexOf(arg); + + while (index > -1) { + list.splice(index, 1); + // Handle firing indexes + if (firing) { + if (index <= firingLength) { + firingLength--; + } + if (index <= firingIndex) { + firingIndex--; + } + } + index = list.indexOf(arg, index); + } + }); + } + return this; + }, + /** + * Check if a given callback is in the list. + * If no argument is given, + * return whether or not list has callbacks attached. + * @method has + * @param {Function} fn + * @return {boolean} + * @member ns.util.callbacks + */ + has: function (fn) { + return fn ? !!list && list.indexOf(fn) > -1 : !!(list && list.length); + }, + /** + * Remove all callbacks from the list + * @method empty + * @return {ns.util.callbacks} self + * @member ns.util.callbacks + */ + empty: function () { + list = []; + firingLength = 0; + return this; + }, + /** + * Have the list do nothing anymore + * @method disable + * @return {ns.util.callbacks} self + * @member ns.util.callbacks + */ + disable: function () { + list = stack = memory = undefined; + return this; + }, + /** + * Is it disabled? + * @method disabled + * @return {boolean} + * @member ns.util.callbacks + */ + disabled: function () { + return !list; + }, + /** + * Lock the list in its current state + * @method lock + * @return {ns.util.callbacks} self + * @member ns.util.callbacks + */ + lock: function () { + stack = undefined; + if (!memory) { + self.disable(); + } + return this; + }, + /** + * Is it locked? + * @method locked + * @return {boolean} stack + * @member ns.util.callbacks + */ + locked: function () { + return !stack; + }, + /** + * Call all callbacks with the given context and + * arguments + * @method fireWith + * @param {Object} context + * @param {Array} args + * @return {ns.util.callbacks} self + * @member ns.util.callbacks + */ + fireWith: function (context, args) { + if (list && (!fired || stack)) { + args = args || []; + args = [context, args.slice ? args.slice() : args]; + if (firing) { + stack.push(args); + } else { + fire(args); + } + } + return this; + }, + /** + * Call all the callbacks with the given arguments + * @method fire + * @return {ns.util.callbacks} self + * @member ns.util.callbacks + */ + fire: function () { + self.fireWith(this, arguments); + return this; + }, + /** + * To know if the callbacks have already been called at + * least once + * @method fired + * @return {boolean} + * @member ns.util.callbacks + */ + fired: function () { + return !!fired; + } + }; + /** + * Adds functions to the callback list + * @method add + * @param {...*} argument + * @member ns.util.bezierCurve + * @private + */ + + add = function (args) { + slice.call(args).forEach(function (arg) { + var type = typeof arg; + + if (type === "function") { + if (!options.unique || !self.has(arg)) { + list.push(arg); + } + } else if (arg && arg.length && type !== "string") { + // Inspect recursively + add(arg); + } + }); + }; + /** + * Fire callbacks + * @method fire + * @param {Array} data + * @member ns.util.bezierCurve + * @private + */ + fire = function (data) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + while (list && firingIndex < firingLength) { + if (list[firingIndex].apply(data[0], data[1]) === false && options.stopOnFalse) { + memory = false; // To prevent further calls using add + break; + } + firingIndex++; + } + firing = false; + if (list) { + if (stack) { + if (stack.length) { + fire(stack.shift()); + } + } else if (memory) { + list = []; + } else { + self.disable(); + } + } + }; + + return self; + }; + + }(window, window.document, ns)); + +/*global window, ns, define, ns */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * #Deferred Utility + * Class creates object which can call registered callback depend from + * state of object.. + * @class ns.util.deferred + * @author Tomasz Lukawski + * @author Maciej Urbanski + * @author Piotr Karny +*/(function (window, document, ns) { + "use strict"; + + var Deferred = function (callback) { + var callbacks = ns.util.callbacks, + object = ns.util.object, + /** + * Register additional action for deferred object + * @property {Array} tuples + * @member ns.util.deferred + * @private + */ + tuples = [ + // action, add listener, listener list, final state + ["resolve", "done", callbacks({once: true, memory: true}), "resolved"], + ["reject", "fail", callbacks({once: true, memory: true}), "rejected"], + ["notify", "progress", callbacks({memory: true})] + ], + state = "pending", + deferred = {}, + promise = { + /** + * Determine the current state of a Deferred object. + * @method state + * @return {"pending" | "resolved" | "rejected"} representing the current state + * @member ns.util.deferred + */ + state: function () { + return state; + }, + /** + * Add handlers to be called when the Deferred object + * is either resolved or rejected. + * @method always + * @return {ns.util.deferred} self + * @member ns.util.deferred + */ + always: function () { + deferred.done(arguments).fail(arguments); + return this; + }, + /** + * Add handlers to be called when the Deferred object + * is resolved, rejected, or still in progress. + * @method then + * @return {Object} returns a new promise + * @member ns.util.deferred + */ + then: function () { /* fnDone, fnFail, fnProgress */ + var functions = arguments; + + return new Deferred(function (newDefer) { + tuples.forEach(function (tuple, i) { + var fn = (typeof functions[i] === "function") && functions[i]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + + deferred[tuple[1]](function () { + var returned = fn && fn.apply(this, arguments); + + if (returned && (typeof returned.promise === "function")) { + returned.promise() + .done(newDefer.resolve) + .fail(newDefer.reject) + .progress(newDefer.notify); + } else { + newDefer[tuple[0] + "With"](this === promise ? newDefer.promise() : this, fn ? [returned] : arguments); + } + }); + }); + functions = null; + }).promise(); + }, + /** + * Get a promise for this deferred. If obj is provided, + * the promise aspect is added to the object + * @method promise + * @param {Object} obj + * @return {Object} return a Promise object + * @member ns.util.deferred + */ + promise: function (obj) { + if (obj) { + return object.merge(obj, promise); + } + return promise; + } + }; + + /** + * alias for promise.then, Keep pipe for back-compat + * @method pipe + * @member ns.util.deferred + */ + promise.pipe = promise.then; + + // Add list-specific methods + + tuples.forEach(function (tuple, i) { + var list = tuple[2], + stateString = tuple[3]; + + // promise[ done | fail | progress ] = list.add + promise[tuple[1]] = list.add; + + // Handle state + if (stateString) { + list.add(function () { + // state = [ resolved | rejected ] + state = stateString; + + // mapping of values [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[i ^ 1][2].disable, tuples[2][2].lock); + } + + // deferred[ resolve | reject | notify ] + deferred[tuple[0]] = function () { + deferred[tuple[0] + "With"](this === deferred ? promise : this, arguments); + return this; + }; + deferred[tuple[0] + "With"] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise(deferred); + + // Call given func if any + if (callback) { + callback.call(deferred, deferred); + } + + // All done! + return deferred; + }; + + ns.util.deferred = Deferred; + }(window, window.document, ns)); + +/*global window, define, ns */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/*jslint nomen: true, plusplus: true */ +/** + * #Popup + * Popup component supports 2 pop-ups: the position-to-window pop-up (like a system pop-up), and the context pop-up. + * + * @since 2.0 + * @author Hyunkook Cho + * @class ns.widget.mobile.Popup + * @extends ns.widget.core.BaseWidget + */ +(function () { + "use strict"; + /** + * Alias for {@link ns.widget.BaseWidget} + * @property {Function} BaseWidget + * @member ns.widget.core.Popup + * @private + */ + var BaseWidget = ns.widget.BaseWidget, + /** + * Alias for class ns.engine + * @property {ns.engine} engine + * @member ns.widget.core.Popup + * @private + */ + engine = ns.engine, + /** + * Alias for class ns.util.object + * @property {Object} objectUtils + * @member ns.widget.core.Popup + * @private + */ + objectUtils = ns.util.object, + /** + * Alias for class ns.util.deferred + * @property {Object} UtilDeferred + * @member ns.widget.core.Popup + * @private + */ + UtilDeferred = ns.util.deferred, + /** + * Alias for class ns.util.selectors + * @property {Object} utilSelector + * @member ns.widget.core.Popup + * @private + */ + utilSelector = ns.util.selectors, + /** + * Alias for class ns.event + * @property {Object} eventUtils + * @member ns.widget.core.Popup + * @private + */ + eventUtils = ns.event, + /** + * Alias for Router, loose requirement + * @property {ns.router.Router} Router + * @member ns.widget.core.Popup + * @private + */ + Router = ns.router && ns.router.Router, + + POPUP_SELECTOR = "[data-role='popup'], .ui-popup", + + Popup = function () { + var self = this, + ui = {}; + + self.selectors = selectors; + self.options = objectUtils.merge({}, Popup.defaults); + self.storedOptions = null; + /** + * Popup state flag + * @property {0|1|2|3} [state=null] + * @member ns.widget.core.Popup + * @private + */ + self.state = states.CLOSED; + + ui.overlay = null; + ui.header = null; + ui.footer = null; + ui.content = null; + ui.container = null; + ui.wrapper = null; + self._ui = ui; + + // event callbacks + self._callbacks = {}; + }, + /** + * Object with default options + * @property {Object} defaults + * @property {string} [options.transition="none"] Sets the default transition for the popup. + * @property {string} [options.positionTo="window"] Sets the element relative to which the popup will be centered. + * @property {boolean} [options.dismissible=true] Sets whether to close popup when a popup is open to support the back button. + * @property {boolean} [options.overlay=true] Sets whether to show overlay when a popup is open. + * @property {boolean|string} [options.header=false] Sets content of header. + * @property {boolean|string} [options.footer=false] Sets content of footer. + * @property {string} [options.content=null] Sets content of popup. + * @property {string} [options.overlayClass=""] Sets the custom class for the popup background, which covers the entire window. + * @property {string} [options.closeLinkSelector="a[data-rel='back']"] Sets selector for close buttons in popup. + * @property {boolean} [options.history=true] Sets whether to alter the url when a popup is open to support the back button. + * @member ns.widget.core.Popup + * @static + */ + defaults = { + transition: "none", + dismissible: true, + overlay: true, + header: false, + footer: false, + content: null, + overlayClass: "", + closeLinkSelector: "[data-rel='back']", + history: null, + closeAfter: null + }, + states = { + DURING_OPENING: 0, + OPENED: 1, + DURING_CLOSING: 2, + CLOSED: 3 + }, + CLASSES_PREFIX = "ui-popup", + /** + * Dictionary for popup related css class names + * @property {Object} classes + * @member ns.widget.core.Popup + * @static + */ + /** + * Toast style of popup + * @style ui-popup-toast + * @member ns.widget.core.Popup + * @wearable + */ + /** + * Toast style of popup with graphic + * @style ui-popup-toast-graphic + * @member ns.widget.core.Popup + * @wearable + */ + classes = { + popup: CLASSES_PREFIX, + active: CLASSES_PREFIX + "-active", + overlay: CLASSES_PREFIX + "-overlay", + header: CLASSES_PREFIX + "-header", + footer: CLASSES_PREFIX + "-footer", + content: CLASSES_PREFIX + "-content", + wrapper: CLASSES_PREFIX + "-wrapper", + toast: CLASSES_PREFIX + "-toast", + toastSmall: CLASSES_PREFIX + "-toast-small", + build: "ui-build" + }, + /** + * Dictionary for popup related selectors + * @property {Object} selectors + * @member ns.widget.core.Popup + * @static + */ + selectors = { + header: "." + classes.header, + content: "." + classes.content, + footer: "." + classes.footer + }, + EVENTS_PREFIX = "popup", + /** + * Dictionary for popup related events + * @property {Object} events + * @member ns.widget.core.Popup + * @static + */ + events = { + /** + * Triggered when the popup has been created in the DOM (via ajax or other) but before all widgets have had an opportunity to enhance the contained markup. + * @event popupshow + * @member ns.widget.core.Popup + */ + show: EVENTS_PREFIX + "show", + /** + * Triggered on the popup after the transition animation has completed. + * @event popuphide + * @member ns.widget.core.Popup + */ + hide: EVENTS_PREFIX + "hide", + /** + * Triggered on the popup we are transitioning to, before the actual transition animation is kicked off. + * @event popupbeforeshow + * @member ns.widget.core.Popup + */ + /* eslint-disable camelcase */ + // we can't change this in this moment because this is part of API + before_show: EVENTS_PREFIX + "beforeshow", + /** + * Triggered on the popup we are transitioning to, before the actual transition animation is kicked off, animation has started. + * @event popuptransitionstart + * @member ns.widget.core.Popup + */ + transition_start: EVENTS_PREFIX + "transitionstart", + /** + * Triggered on the popup we are transitioning away from, before the actual transition animation is kicked off. + * @event popupbeforehide + * @member ns.widget.core.Popup + */ + before_hide: EVENTS_PREFIX + "beforehide" + /* eslint-enable camelcase */ + }, + + prototype = new BaseWidget(); + + Popup.classes = classes; + Popup.events = events; + Popup.defaults = defaults; + Popup.selector = POPUP_SELECTOR; + + /** + * Build the content of popup + * @method _buildContent + * @param {HTMLElement} element + * @protected + * @member ns.widget.core.Popup + */ + prototype._buildContent = function (element) { + var self = this, + ui = self._ui, + selectors = self.selectors, + options = self.options, + content = ui.content || element.querySelector(selectors.content), + footer = ui.footer || element.querySelector(selectors.footer), + elementChildren = [].slice.call(element.childNodes), + elementChildrenLength = elementChildren.length, + i, + node; + + if (!content) { + content = document.createElement("div"); + content.className = classes.content; + for (i = 0; i < elementChildrenLength; ++i) { + node = elementChildren[i]; + if (node !== ui.footer && node !== ui.header) { + content.appendChild(node); + } + } + if (typeof options.content === "string") { + content.innerHTML = options.content; + } + element.insertBefore(content, footer); + } + content.classList.add(classes.content); + ui.content = content; + }; + + /** + * Build the header of popup + * @method _buildHeader + * @param {HTMLElement} element + * @protected + * @member ns.widget.core.Popup + */ + prototype._buildHeader = function (element) { + var self = this, + ui = self._ui, + options = self.options, + selectors = self.selectors, + content = ui.content || element.querySelector(selectors.content), + header = ui.header || element.querySelector(selectors.header); + + if (!header && options.header !== false) { + header = document.createElement("div"); + header.className = classes.header; + if (typeof options.header !== "boolean") { + header.innerHTML = options.header; + } + element.insertBefore(header, content); + } + if (header) { + header.classList.add(classes.header); + } + ui.header = header; + }; + + /** + * Set the header of popup. + * This function is called by function "option" when the option "header" is set. + * @method _setHeader + * @param {HTMLElement} element + * @param {boolean|string} value + * @protected + * @member ns.widget.core.Popup + */ + prototype._setHeader = function (element, value) { + var self = this, + ui = self._ui, + header = ui.header; + + if (header) { + header.parentNode.removeChild(header); + ui.header = null; + } + self.options.header = value; + self._buildHeader(ui.container); + }; + + /** + * Build the footer of popup + * @method _buildFooter + * @param {HTMLElement} element + * @protected + * @member ns.widget.core.Popup + */ + prototype._buildFooter = function (element) { + var self = this, + ui = self._ui, + options = self.options, + footer = ui.footer || element.querySelector(self.selectors.footer); + + if (!footer && options.footer !== false) { + footer = document.createElement("div"); + footer.className = classes.footer; + if (typeof options.footer !== "boolean") { + footer.innerHTML = options.footer; + } + element.appendChild(footer); + } + if (footer) { + footer.classList.add(classes.footer); + } + ui.footer = footer; + }; + + /** + * Set the footer of popup. + * This function is called by function "option" when the option "footer" is set. + * @method _setFooter + * @param {HTMLElement} element + * @param {boolean|string} value + * @protected + * @member ns.widget.core.Popup + */ + prototype._setFooter = function (element, value) { + var self = this, + ui = self._ui, + footer = ui.footer; + + if (footer) { + footer.parentNode.removeChild(footer); + ui.footer = null; + } + self.options.footer = value; + self._buildFooter(ui.container); + }; + + /** + * Build structure of Popup widget + * @method _build + * @param {HTMLElement} element of popup + * @return {HTMLElement} + * @protected + * @member ns.widget.Popup + */ + prototype._build = function (element) { + var self = this, + ui = self._ui, + wrapper, + child = element.firstChild, + elementClassList = element.classList; + + // set class for element + elementClassList.add(classes.popup); + + if (elementClassList.contains(classes.toastSmall)) { + elementClassList.add(classes.toast); + } + + // create wrapper + wrapper = document.createElement("div"); + wrapper.classList.add(classes.wrapper); + ui.wrapper = wrapper; + ui.container = wrapper; + // move all children to wrapper + while (child) { + wrapper.appendChild(child); + child = element.firstChild; + } + // add wrapper and arrow to popup element + element.appendChild(wrapper); + + // build header, footer and content + self._buildHeader(ui.container); + self._buildFooter(ui.container); + self._buildContent(ui.container); + + // set overlay + self._setOverlay(element, self.options.overlay); + + return element; + }; + + /** + * Set overlay + * @method _setOverlay + * @param {HTMLElement} element + * @param {boolean} enable + * @protected + * @member ns.widget.Popup + */ + prototype._setOverlay = function (element, enable) { + var self = this, + overlayClass = self.options.overlayClass, + ui = self._ui, + overlay = ui.overlay; + + // if this popup is not connected with slider, + // we create overlay, which is invisible when + // the value of option overlay is false + /// @TODO: get class from widget + if (!element.classList.contains("ui-slider-popup") && !element.classList.contains(classes.toast)) { + // create overlay + if (!overlay) { + overlay = document.createElement("div"); + + if (element.parentNode) { + element.parentNode.insertBefore(overlay, element); + } else { + ns.warn("Popup is creating on element outside DOM"); + } + + ui.overlay = overlay; + } + overlay.className = classes.overlay + (overlayClass ? " " + overlayClass : ""); + if (enable) { + overlay.style.opacity = ""; + } else { + // if option is set on "false", the overlay is not visible + overlay.style.opacity = 0; + } + } + }; + + /** + * Returns the state of the popup + * @method _isActive + * @protected + * @member ns.widget.core.Popup + */ + prototype._isActive = function () { + var state = this.state; + + return state === states.DURING_OPENING || state === states.OPENED; + }; + + /** + * Returns true if popup is already opened and visible + * @method _isActive + * @protected + * @member ns.widget.core.Popup + */ + prototype._isOpened = function () { + return this.state === states.OPENED; + }; + + /** + * Init widget + * @method _init + * @param {HTMLElement} element + * @protected + * @member ns.widget.core.Popup + */ + prototype._init = function (element) { + var self = this, + selectors = self.selectors, + ui = self._ui, + options = self.options, + elementClassList = self.element.classList; + + ui.header = ui.header || element.querySelector(selectors.header); + ui.footer = ui.footer || element.querySelector(selectors.footer); + ui.content = ui.content || element.querySelector(selectors.content); + ui.wrapper = ui.wrapper || element.querySelector("." + classes.wrapper); + ui.container = ui.wrapper || element; + + // @todo - use selector from page's definition in engine + ui.page = utilSelector.getClosestByClass(element, "ui-page") || window; + + if (elementClassList.contains(classes.toast)) { + options.closeAfter = options.closeAfter || 2000; + } + // if option history is not set in constructor or in HTML + if (options.history === null) { + // for toast we set false for other true + options.history = !elementClassList.contains(classes.toast); + } + }; + + /** + * Set the state of the popup + * @method _setActive + * @param {boolean} active + * @protected + * @member ns.widget.core.Popup + */ + prototype._setActive = function (active) { + var self = this, + activeClass = classes.active, + elementClassList = self.element.classList, + route = Router && Router.getInstance().getRoute("popup"), + options; + + // NOTE: popup's options object is stored in window.history at the router module, + // and this window.history can't store DOM element object. + options = objectUtils.merge({}, self.options, {positionTo: null, link: null}); + + // set state of popup and add proper class + if (active) { + // set global variable + if (route) { + route.setActive(self, options); + } + // add proper class + elementClassList.add(activeClass); + // set state of popup 358 + self.state = states.OPENED; + } else { + // no popup is opened, so set global variable on "null" + if (route) { + route.setActive(null, options); + } + // remove proper class + elementClassList.remove(activeClass); + // set state of popup + self.state = states.CLOSED; + } + }; + + /** + * Bind events + * @method _bindEvents + * @protected + * @member ns.widget.core.Popup + */ + prototype._bindEvents = function () { + var self = this; + + eventUtils.on(self._ui.page, "pagebeforehide", self, false); + eventUtils.on(window, "resize", self, false); + eventUtils.on(document, "click touchstart", self, false); + }; + + + /** + * Unbind events + * @method _bindEvents + * @protected + * @member ns.widget.core.Popup + */ + prototype._unbindEvents = function () { + var self = this; + + eventUtils.off(self._ui.page, "pagebeforehide", self, false); + eventUtils.off(window, "resize", self, false); + eventUtils.off(document, "click touchstart", self, false); + }; + + /** + * Layouting popup structure + * @method layout + * @member ns.widget.core.Popup + */ + prototype._layout = function () { + }; + + /** + * Open the popup + * @method open + * @param {Object=} [options] + * @param {string=} [options.transition] options.transition + * @member ns.widget.core.Popup + */ + prototype.open = function (options) { + var self = this, + newOptions, + onClose = self.close.bind(self); + + + if (!self._isActive()) { + /* + * Some passed options on open need to be kept until popup closing. + * For example, transition parameter should be kept for closing animation. + * On the other hand, fromHashChange or x, y parameter should be removed. + * We store options and restore them on popup closing. + */ + self._storeOpenOptions(options); + + newOptions = objectUtils.merge(self.options, options); + if (!newOptions.dismissible) { + ns.router.Router.getInstance().lock(); + } + + + if (newOptions.closeAfter > 0) { + if (self.element.classList.contains(classes.toast)) { + newOptions.transition = "fade"; + } + self._show(newOptions); + + self._closeTimeout = window.setTimeout(onClose, newOptions.closeAfter); + } else { + self._show(newOptions); + } + } + }; + + + /** + * Close the popup + * @method close + * @param {Object=} [options] + * @param {string=} [options.transition] + * @member ns.widget.core.Popup + */ + prototype.close = function (options) { + var self = this, + newOptions = objectUtils.merge(self.options, options); + + if (self._isActive()) { + clearTimeout(self._closeTimeout); + if (!newOptions.dismissible) { + ns.router.Router.getInstance().unlock(); + } + self._hide(newOptions); + } + }; + + /** + * Store Open options. + * @method _storeOpenOptions + * @param {Object} options + * @protected + * @member ns.widget.core.Popup + */ + prototype._storeOpenOptions = function (options) { + var self = this, + oldOptions = self.options, + storedOptions = {}, + key; + + for (key in options) { + if (options.hasOwnProperty(key)) { + storedOptions[key] = oldOptions[key]; + } + } + + self.storedOptions = storedOptions; + }; + + /** + * Restore Open options and remove some unnecessary ones. + * @method _storeOpenOptions + * @protected + * @member ns.widget.core.Popup + */ + prototype._restoreOpenOptions = function () { + var self = this, + options = self.options, + propertiesToRemove = ["x", "y", "fromHashChange"]; + + // we restore opening values of all options + options = objectUtils.merge(options, self.storedOptions); + // and remove all values which should not be stored + objectUtils.removeProperties(options, propertiesToRemove); + }; + + /** + * Show popup. + * @method _show + * @param {Object} options + * @protected + * @member ns.widget.core.Popup + */ + prototype._show = function (options) { + var self = this, + transitionOptions = objectUtils.merge({}, options), + overlay = self._ui.overlay; + + // set layout + self._layout(self.element); + + // change state of popup + self.state = states.DURING_OPENING; + // set transition + transitionOptions.ext = " in "; + + self.trigger(events.before_show); + // show overlay + if (overlay) { + overlay.style.display = "block"; + } + // start opening animation + self._transition(transitionOptions, self._onShow.bind(self)); + + // animation has started + self.trigger(events.transition_start); + }; + + /** + * Show popup + * @method _onShow + * @protected + * @member ns.widget.core.Popup + */ + prototype._onShow = function () { + var self = this; + + self._setActive(true); + self.trigger(events.show); + }; + + /** + * Hide popup + * @method _hide + * @param {Object} options + * @protected + * @member ns.widget.core.Popup + */ + prototype._hide = function (options) { + var self = this, + isOpened = self._isOpened(), + callbacks = self._callbacks; + + // change state of popup + self.state = states.DURING_CLOSING; + + self.trigger(events.before_hide); + + if (isOpened) { + // popup is opened, so we start closing animation + options.ext = " out "; + self._transition(options, self._onHide.bind(self)); + } else { + // popup is active, but not opened yet (DURING_OPENING), so + // we stop opening animation + if (callbacks.transitionDeferred) { + callbacks.transitionDeferred.reject(); + } + if (callbacks.animationEnd) { + callbacks.animationEnd(); + } + // and set popup as inactive + self._onHide(); + } + }; + + /** + * Hide popup + * @method _onHide + * @protected + * @member ns.widget.core.Popup + */ + prototype._onHide = function () { + var self = this, + overlay = self._ui.overlay; + + self._setActive(false); + + if (overlay) { + overlay.style.display = ""; + } + self._restoreOpenOptions(); + self.trigger(events.hide); + }; + + /** + * Handle events + * @method handleEvent + * @param {Event} event + * @member ns.widget.core.Popup + */ + prototype.handleEvent = function (event) { + var self = this, + router = ns.router.Router.getInstance(); + + switch (event.type) { + case "pagebeforehide": + // we need close active popup if exists + router.close(null, {transition: "none", rel: "popup"}); + break; + case "resize": + self._onResize(event); + break; + case "click": + if (event.target === self._ui.overlay) { + self._onClickOverlay(event); + } + break; + case "touchstart": + if (self.element.classList.contains(classes.toast) && self._isActive()) { + router.close(null, {rel: "popup"}); + } + break; + } + }; + + /** + * Refresh structure + * @method _refresh + * @protected + * @member ns.widget.core.Popup + */ + prototype._refresh = function () { + var self = this; + + self._setOverlay(self.element, self.options.overlay); + }; + + /** + * Callback function fires after clicking on overlay. + * @method _onClickOverlay + * @param {Event} event + * @protected + * @member ns.widget.core.Popup + */ + prototype._onClickOverlay = function (event) { + var options = this.options; + + event.preventDefault(); + event.stopPropagation(); + + if (options.dismissible) { + ns.router.Router.getInstance().close(null, {rel: "popup"}); + } + }; + + /** + * Callback function fires on resizing + * @method _onResize + * @protected + * @member ns.widget.core.Popup + */ + prototype._onResize = function () { + if (this._isOpened()) { + this._refresh(); + } + }; + + function clearAnimation(self, transitionClass, deferred) { + var element = self.element, + elementClassList = element.classList, + overlay = self._ui.overlay, + animationEndCallback = self._callbacks.animationEnd; + + // remove callbacks on animation events + element.removeEventListener("animationend", animationEndCallback, false); + element.removeEventListener("webkitAnimationEnd", animationEndCallback, false); + element.removeEventListener("mozAnimationEnd", animationEndCallback, false); + element.removeEventListener("oAnimationEnd", animationEndCallback, false); + element.removeEventListener("msAnimationEnd", animationEndCallback, false); + + // clear classes + transitionClass.split(" ").forEach(function (currentClass) { + currentClass = currentClass.trim(); + if (currentClass.length > 0) { + elementClassList.remove(currentClass); + if (overlay) { + overlay.classList.remove(currentClass); + } + } + }); + if (deferred.state() === "pending") { + // we resolve only pending (not rejected) deferred + deferred.resolve(); + } + } + + function setTransitionDeferred(self, resolve) { + var deferred = new UtilDeferred(); + + deferred.then(function () { + if (deferred === self._callbacks.transitionDeferred) { + resolve(); + } + }); + + self._callbacks.transitionDeferred = deferred; + return deferred; + } + + /** + * Animate popup opening/closing + * @method _transition + * @protected + * @param {Object} [options] + * @param {string=} [options.transition] + * @param {string=} [options.ext] + * @param {?Function} [resolve] + * @member ns.widget.core.Popup + */ + prototype._transition = function (options, resolve) { + var self = this, + transition = options.transition || self.options.transition || "none", + transitionClass = transition + options.ext, + element = self.element, + elementClassList = element.classList, + overlayClassList, + deferred, + animationEndCallback; + + if (self._ui.overlay) { + overlayClassList = self._ui.overlay.classList; + } + + deferred = setTransitionDeferred(self, resolve); + + if (transition !== "none") { + // set animationEnd callback + animationEndCallback = clearAnimation.bind(null, self, transitionClass, deferred); + self._callbacks.animationEnd = animationEndCallback; + + // add animation callbacks + element.addEventListener("animationend", animationEndCallback, false); + element.addEventListener("webkitAnimationEnd", animationEndCallback, false); + element.addEventListener("mozAnimationEnd", animationEndCallback, false); + element.addEventListener("oAnimationEnd", animationEndCallback, false); + element.addEventListener("msAnimationEnd", animationEndCallback, false); + // add transition classes + transitionClass.split(" ").forEach(function (currentClass) { + currentClass = currentClass.trim(); + if (currentClass.length > 0) { + elementClassList.add(currentClass); + + if (overlayClassList) { + overlayClassList.add(currentClass); + } + + } + }); + } else { + if (!ns.getConfig("noAsync", false)) { + window.setTimeout(deferred.resolve, 0); + } else { + deferred.resolve(); + } + } + return deferred; + }; + + /** + * Destroy popup + * @method _destroy + * @protected + * @member ns.widget.core.Popup + */ + prototype._destroy = function () { + var self = this, + element = self.element, + ui = self._ui, + wrapper = ui.wrapper, + child; + + if (wrapper) { + // restore all children from wrapper + child = wrapper.firstChild; + while (child) { + element.appendChild(child); + child = wrapper.firstChild; + } + + if (wrapper.parentNode) { + wrapper.parentNode.removeChild(wrapper); + } + } + + self._unbindEvents(element); + self._setOverlay(element, false); + + ui.wrapper = null; + }; + + Popup.prototype = prototype; + + ns.widget.core.Popup = Popup; + + engine.defineWidget( + "Popup", + POPUP_SELECTOR, + [ + "open", + "close", + "reposition" + ], + Popup, + "core" + ); + }()); + +/*global window, define, ns */ +/*jslint nomen: true */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * #Route Popup + * Support class for router to control changing popups. + * @class ns.router.route.popup + * @author Maciej Urbanski + * @author Damian Osipiuk + */ +(function (window, document, ns) { + "use strict"; + var + /** + * @property {Object} Popup Alias for {@link ns.widget.Popup} + * @member ns.router.route.popup + * @private + * @static + */ + Popup = ns.widget.core.Popup, + util = ns.util, + routePopup = { + /** + * Object with default options + * @property {Object} defaults + * @property {string} [defaults.transition='none'] Sets the animation used during change + * of popup. + * @property {?HTMLElement} [defaults.container=null] Sets container of element. + * @property {boolean} [defaults.volatileRecord=true] Sets if the current history entry + * will be modified or a new one will be created. + * @member ns.router.route.popup + * @static + */ + defaults: { + transition: "none", + container: null, + volatileRecord: true + }, + /** + * Popup Element Selector + * @property {string} filter + * @member ns.router.route.popup + * @static + */ + filter: "." + Popup.classes.popup, + /** + * Storage variable for active popup + * @property {?HTMLElement} activePopup + * @member ns.router.route.popup + * @static + */ + activePopup: null, + /** + * Dictionary for popup related event types + * @property {Object} events + * @property {string} [events.POPUP_HIDE='popuphide'] + * @member ns.router.route.popup + * @static + */ + events: { + POPUP_HIDE: "popuphide" + }, + + /** + * Alias for {@link ns.util.path} + * @property {Object} path + * @member ns.router.route.popup + * @protected + * @static + */ + _path: ns.util.path, + /** + * Alias for {@link ns.router.history} + * @property {Object} history + * @member ns.router.route.popup + * @protected + * @static + */ + _history: ns.history + }, + /** + * Alias for {@link ns.engine} + * @property {Object} engine + * @member ns.router.route.popup + * @private + * @static + */ + engine = ns.engine, + /** + * Alias for {@link ns.util.selectors} + * @property {Object} utilSelector + * @member ns.router.route.popup + * @private + * @static + */ + utilSelector = ns.util.selectors, + /** + * Alias for {@link ns.util.DOM} + * @property {Object} DOM + * @member ns.router.route.popup + * @private + * @static + */ + DOM = ns.util.DOM, + /** + * Alias for Object utils + * @method slice + * @member ns.router.route.popup + * @private + * @static + */ + object = ns.util.object, + /** + * Popup's hash added to url + * @property {string} popupHashKey + * @member ns.router.route.popup + * @private + * @static + */ + popupHashKey = "popup=true", + /** + * Regexp for popup's hash + * @property {RegExp} popupHashKeyReg + * @member ns.router.route.popup + * @private + * @static + */ + popupHashKeyReg = /([&|\?]popup=true)/; + + /** + * Tries to find a popup element matching id and filter (selector). + * Adds data url attribute to found page, sets page = null when nothing found. + * @method findPopupAndSetDataUrl + * @param {string} id + * @param {string} filter + * @return {HTMLElement} + * @member ns.router.route.popup + * @private + * @static + */ + function findPopupAndSetDataUrl(id, filter) { + var popup, + hashReg = /^#/; + + id = id.replace(hashReg, ""); + popup = document.getElementById(id); + + if (popup && utilSelector.matchesSelector(popup, filter)) { + DOM.setNSData(popup, "url", "#" + id); + } else { + // if we matched any element, but it doesn't match our filter + // reset page to null + popup = null; + } + // probably there is a need for running onHashChange while going back to a history entry + // without state, eg. manually entered #fragment. This may not be a problem on target device + return popup; + } + + routePopup.orderNumber = 100; + /** + * This method returns default options for popup router. + * @method option + * @return {Object} + * @member ns.router.route.popup + * @static + */ + routePopup.option = function () { + var defaults = object.merge({}, routePopup.defaults); + + defaults.transition = ns.getConfig("popupTransition", defaults.transition); + return defaults; + }; + + /** + * This method sets active popup and manages history. + * @method setActive + * @param {?ns.widget.core.popup} activePopup + * @param {Object} options + * @member ns.router.route.popup + * @static + */ + routePopup.setActive = function (activePopup, options) { + var url, + pathLocation = routePopup._path.getLocation(), + documentUrl = pathLocation.replace(popupHashKeyReg, ""); + + this.activePopup = activePopup; + + if (activePopup) { + // If popup is being opened, the new state is added to history. + if (options && !options.fromHashChange && options.history) { + url = routePopup._path.addHashSearchParams(documentUrl, popupHashKey); + routePopup._history.replace(options, "", url); + this.active = true; + } + } else if (pathLocation !== documentUrl) { + // If popup is being closed, the history.back() is called + // but only if url has special hash. + // Url is changed after opening animation and in some cases, + // the popup is closed before this animation and then the history.back + // could cause undesirable change of page. + this.active = false; + routePopup._history.back(); + } + }; + + /** + * This method opens popup if no other popup is opened. + * It also changes history to show that popup is opened. + * If there is already active popup, it will be closed. + * @method open + * @param {HTMLElement|string} toPopup + * @param {Object} options + * @param {"page"|"popup"|"external"} [options.rel = 'popup'] Represents kind of link as + * 'page' or 'popup' or 'external' for linking to another domain. + * @param {string} [options.transition = 'none'] Sets the animation used during change of + * popup. + * @param {boolean} [options.reverse = false] Sets the direction of change. + * @param {boolean} [options.fromHashChange = false] Sets if will be changed after hashchange. + * @param {boolean} [options.showLoadMsg = true] Sets if message will be shown during loading. + * @param {number} [options.loadMsgDelay = 0] Sets delay time for the show message during + * loading. + * @param {boolean} [options.dataUrl] Sets if page has url attribute. + * @param {string} [options.container = null] Selector for container. + * @param {boolean} [options.volatileRecord=true] Sets if the current history entry will be + * modified or a new one will be created. + * @param {Event} event + * @member ns.router.route.popup + * @static + */ + routePopup.open = function (toPopup, options, event) { + var self = this, + popup, + router = ns.router.Router.getInstance(), + events = self.events, + removePopup = function () { + document.removeEventListener(events.POPUP_HIDE, removePopup, false); + toPopup.parentNode.removeChild(toPopup); + self.activePopup = null; + }, + openPopup = function () { + var positionTo = options["position-to"], + touch; + // add such option only if it exists + + if (positionTo) { + options.positionTo = positionTo; + } + if (event) { + touch = event.touches ? event.touches[0] : event; + options.x = touch.clientX; + options.y = touch.clientY; + } + + document.removeEventListener(events.POPUP_HIDE, openPopup, false); + popup = engine.instanceWidget(toPopup, "Popup", options); + popup.open(options); + self.activePopup = popup; + self.active = popup.options.history; + }, + activePage = router.container.getActivePage(), + container; + + if (DOM.getNSData(toPopup, "external") === true) { + container = options.container ? + activePage.element.querySelector(options.container) : activePage.element; + if (toPopup.parentNode !== container) { + toPopup = util.importEvaluateAndAppendElement(toPopup, container); + } + document.addEventListener(routePopup.events.POPUP_HIDE, removePopup, false); + } + + if (self.hasActive()) { + document.addEventListener(events.POPUP_HIDE, openPopup, false); + if (!self.close()) { + openPopup(); + } + } else { + openPopup(); + } + }; + + /** + * This method closes active popup. + * @method close + * @param {ns.widget.core.Popup} [activePopup] + * @param {Object} options + * @param {string} [options.transition] + * @param {string} [options.ext= in ui-pre-in] options.ext + * @member ns.router.route.popup + * @protected + * @static + */ + routePopup.close = function (activePopup, options) { + var popupOptions, + pathLocation = routePopup._path.getLocation(), + documentUrl = pathLocation.replace(popupHashKeyReg, ""); + + options = options || {}; + + if (activePopup && !(activePopup instanceof Popup)) { + activePopup = engine.instanceWidget(activePopup, "Popup", options); + } + activePopup = activePopup || this.activePopup; + + // if popup is active + if (activePopup) { + popupOptions = activePopup.options; + // we check if it changed the history + if (popupOptions.history && pathLocation !== documentUrl) { + // and then set new options for popup + popupOptions.transition = options.transition || popupOptions.transition; + popupOptions.ext = options.ext || popupOptions.ext; + // unlock the router if it was locked + if (!popupOptions.dismissible) { + ns.router.Router.getInstance().unlock(); + } + // and call history.back() + routePopup._history.back(); + } else { + // if popup did not change the history, we close it normally + activePopup.close(options); + } + return true; + } + return false; + }; + + /** + * This method handles hash change. + * It closes opened popup. + * @method onHashChange + * @param {string} url + * @param {Object} options + * @return {boolean} + * @member ns.router.route.popup + * @static + */ + routePopup.onHashChange = function (url, options) { + var activePopup = this.activePopup; + + if (activePopup) { + activePopup.close(options); + // Default routing setting cause to rewrite further window history + // even if popup has been closed + // To prevent this onHashChange after closing popup we need to change + // disable volatile mode to allow pushing new history elements + if (this.active) { + this.active = false; + return true; + } + } + return false; + }; + + /** + * On open fail, currently never used + * @method onOpenFailed + * @member ns.router.route.popup + * @return {null} + * @static + */ + routePopup.onOpenFailed = function (/* options */) { + return null; + }; + + /** + * This method finds popup by data-url. + * @method find + * @param {string} absUrl Absolute path to opened popup + * @return {HTMLElement} Element of popup + * @member ns.router.route.popup + */ + routePopup.find = function (absUrl) { + var self = this, + dataUrl = self._createDataUrl(absUrl), + activePage = ns.router.Router.getInstance().getContainer().getActivePage(), + popup; + + popup = activePage.element.querySelector("[data-url='" + dataUrl + "']" + self.filter); + + if (!popup && dataUrl && !routePopup._path.isPath(dataUrl)) { + popup = findPopupAndSetDataUrl(dataUrl, self.filter); + } + + return popup; + }; + + /** + * This method parses HTML and runs scripts from parsed code. + * Fetched external scripts if required. + * @method parse + * @param {string} html HTML code to parse + * @param {string} absUrl Absolute url for parsed popup + * @return {HTMLElement} + * @member ns.router.route.popup + */ + routePopup.parse = function (html, absUrl) { + var self = this, + popup, + dataUrl = self._createDataUrl(absUrl); + + popup = html.querySelector(self.filter); + + if (popup) { + // TODO tagging a popup with external to make sure that embedded popups aren't + // removed by the various popup handling code is bad. Having popup handling code + // in many places is bad. Solutions post 1.0 + DOM.setNSData(popup, "url", dataUrl); + DOM.setNSData(popup, "external", true); + } + + return popup; + }; + + /** + * Convert url to data-url + * @method _createDataUrl + * @param {string} absoluteUrl + * @return {string} + * @member ns.router.route.popup + * @protected + * @static + */ + routePopup._createDataUrl = function (absoluteUrl) { + return routePopup._path.convertUrlToDataUrl(absoluteUrl); + }; + + /** + * Return true if active popup exists. + * @method hasActive + * @return {boolean} + * @member ns.router.route.popup + * @static + */ + routePopup.hasActive = function () { + return this.active; + }; + + /** + * Returns active popup. + * @method getActive + * @return {?ns.widget.core.Popup} + * @member ns.router.route.popup + * @static + */ + routePopup.getActive = function () { + return this.activePopup; + }; + + /** + * Returns element of active popup. + * @method getActiveElement + * @return {HTMLElement} + * @member ns.router.route.popup + * @static + */ + routePopup.getActiveElement = function () { + var active = this.getActive(); + + return active && active.element; + }; + + ns.router.route.popup = routePopup; + + }(window, window.document, ns)); + +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* global window, define, ns, CustomEvent */ +/* + * @class ns.event.gesture + */ +(function (ns) { + "use strict"; + var event = ns.event, + gesture = function (elem, options) { + return new ns.event.gesture.Instance(elem, options); + }; + + /** + * Default values for Gesture feature + * @property {Object} defaults + * @property {boolean} [defaults.triggerEvent=false] + * @property {number} [defaults.updateVelocityInterval=16] + * Interval in which Gesture recalculates current velocity in ms + * @property {number} [defaults.estimatedPointerTimeDifference=15] + * pause time threshold.. tune the number to up if it is slow + * @member ns.event.gesture + * @static + */ + gesture.defaults = { + triggerEvent: false, + updateVelocityInterval: 16, + estimatedPointerTimeDifference: 15 + }; + + /** + * Dictionary of orientation + * @property {Object} Orientation + * @property {1} Orientation.VERTICAL vertical orientation + * @property {2} Orientation.HORIZONTAL horizontal orientation + * @member ns.event.gesture + * @static + */ + gesture.Orientation = { + VERTICAL: "vertical", + HORIZONTAL: "horizontal" + }; + + /** + * Dictionary of direction + * @property {Object} Direction + * @property {1} Direction.UP up + * @property {2} Direction.DOWN down + * @property {3} Direction.LEFT left + * @property {4} Direction.RIGHT right + * @member ns.event.gesture + * @static + */ + gesture.Direction = { + UP: "up", + DOWN: "down", + LEFT: "left", + RIGHT: "right" + }; + + /** + * Dictionary of gesture events state + * @property {Object} Event + * @property {"start"} Event.START start + * @property {"move"} Event.MOVE move + * @property {"end"} Event.END end + * @property {"cancel"} Event.CANCEL cancel + * @property {"blocked"} Event.BLOCKED blocked + * @member ns.event.gesture + * @static + */ + gesture.Event = { + START: "start", + MOVE: "move", + END: "end", + CANCEL: "cancel", + BLOCKED: "blocked" + }; + + /** + * Dictionary of gesture events flags + * @property {Object} Result + * @property {number} [Result.PENDING=1] is pending + * @property {number} [Result.RUNNING=2] is running + * @property {number} [Result.FINISHED=4] is finished + * @property {number} [Result.BLOCK=8] is blocked + * @member ns.event.gesture + * @static + */ + gesture.Result = { + PENDING: 1, + RUNNING: 2, + FINISHED: 4, + BLOCK: 8 + }; + + /** + * Create plugin namespace. + * @property {Object} plugin + * @member ns.event.gesture + * @static + */ + gesture.plugin = {}; + + /** + * Create object of Detector + * @method createDetector + * @param {string} gesture + * @param {HTMLElement} eventSender + * @param {Object} options + * @return {ns.event.gesture.Gesture} + * @member ns.event.gesture + * @static + */ + gesture.createDetector = function (gesture, eventSender, options) { + if (!gesture.plugin[gesture]) { + throw gesture + " gesture is not supported"; + } + return new gesture.plugin[gesture](eventSender, options); + }; + + event.gesture = gesture; + }(ns)); + +/*global window, ns, define, ns */ +/*jslint nomen: true */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * #Drawer Widget + * Core Drawer widget is a base for creating Drawer widgets for profiles. It + * provides drawer functionality - container with ability to open and close with + * an animation. + * + * ### Positioning Drawer left / right (option) + * To change position of a Drawer please set data-position attribute of Drawer + * element to: + * + * - left (left position, default) + * - right (right position) + * + * ##Opening / Closing Drawer + * To open / close Drawer one can use open() and close() methods. + * + * ##Checking if Drawer is opened. + * To check if Drawer is opened use widget`s isOpen() method. + * + * ##Creating widget + * Core drawer is a base class - examples of creating widgets are described in + * documentation of profiles + * + * @class ns.widget.core.Drawer + * @extends ns.widget.BaseWidget + * @author Hyeoncheol Choi + */ +(function (document, ns) { + "use strict"; + /** + * @property {Object} Widget Alias for {@link ns.widget.BaseWidget} + * @member ns.widget.core.Drawer + * @private + * @static + */ + var BaseWidget = ns.widget.BaseWidget, + /** + * @property {Object} selectors Alias for class ns.util.selectors + * @member ns.widget.core.Drawer + * @private + * @static + * @readonly + */ + selectors = ns.util.selectors, + utilDOM = ns.util.DOM, + events = ns.event, + history = ns.history, + Gesture = ns.event.gesture, + Page = ns.widget.core.Page, + STATE = { + CLOSED: "closed", + OPENED: "opened", + SLIDING: "sliding", + SETTLING: "settling" + }, + /** + * Events + * @event draweropen Event triggered then the drawer is opened. + * @event drawerclose Event triggered then the drawer is closed. + * @member ns.widget.core.Drawer + */ + CUSTOM_EVENTS = { + OPEN: "draweropen", + CLOSE: "drawerclose" + }, + /** + * Default values + */ + DEFAULT = { + WIDTH: 240, + DURATION: 300, + POSITION: "left" + }, + /** + * Drawer constructor + * @method Drawer + */ + Drawer = function () { + var self = this; + /** + * Drawer field containing options + * @property {string} options.position Position of Drawer ("left" or "right") + * @property {number} options.width Width of Drawer + * @property {number} options.duration Duration of Drawer entrance animation + * @property {boolean} options.closeOnClick If true Drawer will be closed on overlay + * @property {boolean} options.overlay Sets whether to show an overlay when Drawer is open. + * @property {string} options.drawerTarget Set drawer target element as the css selector + * @property {boolean} options.enable Enable drawer component + * @property {number} options.dragEdge Set the area that can open the drawer as drag gesture in drawer target element + * @member ns.widget.core.Drawer + */ + + self.options = { + position: DEFAULT.POSITION, + width: DEFAULT.WIDTH, + duration: DEFAULT.DURATION, + closeOnClick: true, + overlay: true, + drawerTarget: "." + Page.classes.uiPage, + enable: true, + dragEdge: 1 + }; + + self._pageSelector = null; + + self._isDrag = false; + self._state = STATE.CLOSED; + self._settlingType = STATE.CLOSED; + self._translatedX = 0; + + self._ui = {}; + + self._eventBoundElement = null; + self._drawerOverlay = null; + }, + /** + * Dictionary object containing commonly used widget classes + * @property {Object} classes + * @member ns.widget.core.Drawer + * @private + * @static + * @readonly + */ + classes = { + page: Page.classes.uiPage, + drawer: "ui-drawer", + left: "ui-drawer-left", + right: "ui-drawer-right", + overlay: "ui-drawer-overlay", + open: "ui-drawer-open", + close: "ui-drawer-close" + }, + /** + * {Object} Drawer widget prototype + * @member ns.widget.core.Drawer + * @private + * @static + */ + prototype = new BaseWidget(); + + Drawer.prototype = prototype; + Drawer.classes = classes; + + /** + * Unbind drag events + * @method unbindDragEvents + * @param {Object} self + * @param {HTMLElement} element + * @member ns.widget.core.Drawer + * @private + * @static + */ + function unbindDragEvents(self, element) { + var overlayElement = self._ui.drawerOverlay; + + events.disableGesture(element); + events.off(element, "drag dragstart dragend dragcancel swipe swipeleft swiperight vmouseup", self, false); + events.prefixedFastOff(self.element, "transitionEnd", self, false); + events.off(window, "resize", self, false); + if (overlayElement) { + events.off(overlayElement, "vclick", self, false); + } + } + + /** + * Bind drag events + * @method bindDragEvents + * @param {Object} self + * @param {HTMLElement} element + * @member ns.widget.core.Drawer + * @private + * @static + */ + function bindDragEvents(self, element) { + var overlayElement = self._ui.drawerOverlay; + + self._eventBoundElement = element; + + events.enableGesture( + element, + + new Gesture.Drag(), + new Gesture.Swipe({ + orientation: Gesture.Orientation.HORIZONTAL + }) + ); + + events.on(element, "drag dragstart dragend dragcancel swipe swipeleft swiperight vmouseup", self, false); + events.prefixedFastOn(self.element, "transitionEnd", self, false); + events.on(window, "resize", self, false); + if (overlayElement) { + events.on(overlayElement, "vclick", self, false); + } + } + + /** + * Handle events + * @method handleEvent + * @param {Event} event + * @member ns.widget.core.Drawer + */ + prototype.handleEvent = function (event) { + var self = this; + + switch (event.type) { + case "drag": + self._onDrag(event); + break; + case "dragstart": + self._onDragStart(event); + break; + case "dragend": + self._onDragEnd(event); + break; + case "dragcancel": + self._onDragCancel(event); + break; + case "vmouseup": + self._onMouseup(event); + break; + case "swipe": + case "swipeleft": + case "swiperight": + self._onSwipe(event); + break; + case "vclick": + self._onClick(event); + break; + case "transitionend": + case "webkitTransitionEnd": + case "mozTransitionEnd": + case "oTransitionEnd": + case "msTransitionEnd": + self._onTransitionEnd(event); + break; + case "resize": + self._onResize(event); + break; + } + }; + + /** + * MouseUp event handler + * @method _onMouseup + * @member ns.widget.core.Drawer + * @protected + */ + prototype._onMouseup = function () { + var self = this; + + if (self._state === STATE.SLIDING) { + self.close(); + } + }; + /** + * Click event handler + * @method _onClick + * @member ns.widget.core.Drawer + * @protected + */ + prototype._onClick = function () { + var self = this; + + if (self._state === STATE.OPENED) { + self.close(); + } + }; + + /** + * Resize event handler + * @method _onResize + * @member ns.widget.core.Drawer + * @protected + */ + prototype._onResize = function () { + var self = this; + // resize event handler + + self._refresh(); + }; + + /** + * webkitTransitionEnd event handler + * @method _onTransitionEnd + * @member ns.widget.core.Drawer + * @protected + */ + prototype._onTransitionEnd = function () { + var self = this, + position = self.options.position, + drawerOverlay = self._drawerOverlay; + + if (self._state === STATE.SETTLING) { + if (self._settlingType === STATE.OPENED) { + self.trigger(CUSTOM_EVENTS.OPEN, { + position: position + }); + self._setActive(true); + self._state = STATE.OPENED; + } else { + self.close(); + self.trigger(CUSTOM_EVENTS.CLOSE, { + position: position + }); + self._setActive(false); + self._state = STATE.CLOSED; + if (drawerOverlay) { + drawerOverlay.style.visibility = "hidden"; + } + } + } + }; + + /** + * Swipe event handler + * @method _onSwipe + * @protected + * @param {Event} event + * @member ns.widget.core.Drawer + */ + prototype._onSwipe = function (event) { + var self = this, + direction, + options = self.options; + + // Now mobile has two swipe event + if (event.detail) { + direction = event.detail.direction === "left" ? "right" : "left"; + } else if (event.type === "swiperight") { + direction = "left"; + } else if (event.type === "swipeleft") { + direction = "right"; + } + if (options.enable && self._isDrag && options.position === direction) { + self.open(); + self._isDrag = false; + } + }; + /** + * Dragstart event handler + * @method _onDragStart + * @protected + * @param {Event} event + * @member ns.widget.core.Drawer + */ + prototype._onDragStart = function (event) { + var self = this; + + if (self._state === STATE.OPENED) { + return; + } + if (self.options.enable && !self._isDrag && self._state !== STATE.SETTLING && self._checkSideEdge(event)) { + self._isDrag = true; + } else { + self.close(); + } + }; + /** + * Drag event handler + * @method _onDrag + * @protected + * @param {Event} event + * @member ns.widget.core.Drawer + */ + prototype._onDrag = function (event) { + var self = this, + deltaX = event.detail.deltaX, + options = self.options, + translatedX = self._translatedX, + movedX; + + if (options.enable && self._isDrag && self._state !== STATE.SETTLING) { + if (options.position === "left") { + movedX = -options.width + deltaX + translatedX; + if (movedX < 0) { + self._translate(movedX, 0); + } + } else { + movedX = window.innerWidth + deltaX - translatedX; + if (movedX > 0 && movedX > window.innerWidth - options.width) { + self._translate(movedX, 0); + } + } + } + }; + /** + * DragEnd event handler + * @method _onDragEnd + * @protected + * @param {Event} event + * @member ns.widget.core.Drawer + */ + prototype._onDragEnd = function (event) { + var self = this, + options = self.options, + detail = event.detail; + + if (options.enable && self._isDrag) { + if (Math.abs(detail.deltaX) > options.width / 2) { + self.open(); + } else if (self._state !== STATE.SETTLING) { + self.close(); + } + } + self._isDrag = false; + }; + + /** + * DragCancel event handler + * @method _onDragCancel + * @protected + * @member ns.widget.core.Drawer + */ + prototype._onDragCancel = function () { + var self = this; + + if (self.options.enable && self._isDrag) { + self.close(); + } + self._isDrag = false; + }; + /** + * Drawer translate function + * @method _translate + * @param {number} x + * @param {number} duration + * @member ns.widget.core.Drawer + * @protected + */ + prototype._translate = function (x, duration) { + var self = this, + element = self.element; + + if (self._state !== STATE.SETTLING) { + self._state = STATE.SLIDING; + } + + if (duration) { + utilDOM.setPrefixedStyle(element, "transition", utilDOM.getPrefixedValue("transform " + duration / 1000 + "s ease-out")); + } + + // there should be a helper for this :( + utilDOM.setPrefixedStyle(element, "transform", "translate3d(" + x + "px, 0px, 0px)"); + if (self.options.overlay) { + self._setOverlay(x); + } + if (!duration) { + self._onTransitionEnd(); + } + + }; + + /** + * Set overlay opacity and visibility + * @method _setOverlay + * @param {number} x + * @member ns.widget.core.Drawer + * @protected + */ + prototype._setOverlay = function (x) { + var self = this, + options = self.options, + overlay = self._ui.drawerOverlay, + overlayStyle = overlay.style, + absX = Math.abs(x), + ratio = options.position === "right" ? absX / window.innerWidth : absX / options.width; + + if (ratio < 1) { + overlayStyle.visibility = "visible"; + } else { + overlayStyle.visibility = "hidden"; + } + overlayStyle.opacity = 1 - ratio; + }; + + /** + * Set active status in drawer router + * @method _setActive + * @param {boolean} active + * @member ns.widget.core.Drawer + * @protected + */ + prototype._setActive = function (active) { + var self = this, + route = ns.router.getInstance().getRoute("drawer"); + + if (active) { + route.setActive(self); + } else { + route.setActive(null); + } + }; + + /** + * Build structure of Drawer widget + * @method _build + * @param {HTMLElement} element + * @return {HTMLElement} Returns built element + * @member ns.widget.core.Drawer + * @protected + */ + prototype._build = function (element) { + var self = this, + ui = self._ui, + options = self.options, + targetElement; + + element.classList.add(classes.drawer); + element.style.top = 0; + targetElement = selectors.getClosestBySelector(element, options.drawerTarget); + + if (targetElement) { + targetElement.appendChild(element); + targetElement.style.overflowX = "hidden"; + } + + if (self.options.overlay) { + ui.drawerOverlay = self._createOverlay(element); + ui.drawerOverlay.style.visibility = "hidden"; + } + + if (!ui.placeholder) { + ui.placeholder = document.createComment(element.id + "-placeholder"); + element.parentNode.insertBefore(ui.placeholder, element); + } + ui.targetElement = targetElement; + return element; + }; + + /** + * Initialization of Drawer widget + * @method _init + * @param {HTMLElement} element + * @member ns.widget.core.Drawer + * @protected + */ + prototype._init = function (element) { + var self = this, + ui = self._ui; + + ui.drawerPage = selectors.getClosestByClass(element, classes.page); + ui.drawerPage.style.overflowX = "hidden"; + self._initLayout(); + return element; + }; + + /** + * init Drawer widget layout + * @method _initLayout + * @protected + * @member ns.widget.core.Drawer + */ + prototype._initLayout = function () { + var self = this, + options = self.options, + element = self.element, + elementStyle = element.style, + ui = self._ui, + overlayStyle = ui.drawerOverlay ? ui.drawerOverlay.style : null, + height; + + options.width = options.width || ui.targetElement.offsetWidth; + height = ui.targetElement.offsetHeight; + + elementStyle.width = (options.width !== 0) ? options.width + "px" : "100%"; + elementStyle.height = (height !== 0) ? height + "px" : "100%"; + elementStyle.top = "0"; + + if (overlayStyle) { + overlayStyle.width = window.innerWidth + "px"; + overlayStyle.height = window.innerHeight + "px"; + overlayStyle.top = 0; + } + if (options.position === "right") { + element.classList.add(classes.right); + self._translate(window.innerWidth, 0); + } else { + // left or default + element.classList.add(classes.left); + self._translate(-options.width, 0); + } + + self._state = STATE.CLOSED; + }; + + /** + * Provides translation if position is set to right + * @method _translateRight + * @member ns.widget.core.Drawer + * @protected + */ + prototype._translateRight = function () { + var self = this, + options = self.options; + + if (options.position === "right") { + // If drawer position is right, drawer should be moved right side + if (self._state === STATE.OPENED) { + // drawer opened + self._translate(window.innerWidth - options.width, 0); + } else { + // drawer closed + self._translate(window.innerWidth, 0); + } + } + }; + + /** + * Check dragstart event whether triggered on side edge area or not + * @method _checkSideEdge + * @protected + * @param {Event} event + * @member ns.widget.core.Drawer + */ + prototype._checkSideEdge = function (event) { + var self = this, + detail = event.detail, + eventClientX = detail.pointer.clientX - detail.estimatedDeltaX, + options = self.options, + position = options.position, + boundElement = self._eventBoundElement, + boundElementOffsetWidth = boundElement.offsetWidth, + boundElementRightEdge = boundElement.offsetLeft + boundElementOffsetWidth, + dragStartArea = boundElementOffsetWidth * options.dragEdge; + + return ((position === "left" && eventClientX > 0 && eventClientX < dragStartArea) || + (position === "right" && eventClientX > boundElementRightEdge - dragStartArea && + eventClientX < boundElementRightEdge)); + }; + /** + * Refreshes Drawer widget + * @method _refresh + * @member ns.widget.core.Drawer + * @protected + */ + prototype._refresh = function () { + // Drawer layout has been set by parent element layout + var self = this; + + self._translateRight(); + self._initLayout(); + }; + /** + * Creates Drawer overlay element + * @method _createOverlay + * @param {HTMLElement} element + * @member ns.widget.core.Drawer + * @protected + */ + prototype._createOverlay = function (element) { + var overlayElement = document.createElement("div"); + + overlayElement.classList.add(classes.overlay); + element.parentNode.insertBefore(overlayElement, element); + + return overlayElement; + }; + + /** + * Binds events to a Drawer widget + * @method _bindEvents + * @member ns.widget.core.Drawer + * @protected + */ + prototype._bindEvents = function () { + var self = this, + targetElement = self._ui.targetElement; + + bindDragEvents(self, targetElement); + }; + + /** + * Enable Drawer widget + * @method _enable + * @protected + * @member ns.widget.core.Drawer + */ + prototype._enable = function () { + this._oneOption("enable", true); + }; + + /** + * Disable Drawer widget + * @method _disable + * @protected + * @member ns.widget.core.Drawer + */ + prototype._disable = function () { + this._oneOption("enable", false); + }; + + /** + * Checks Drawer status + * @method isOpen + * @member ns.widget.core.Drawer + * @return {boolean} Returns true if Drawer is open + */ + prototype.isOpen = function () { + return (this._state === STATE.OPENED); + }; + + /** + * Opens Drawer widget + * @method open + * @param {number} [duration] Duration for opening, if is not set then method take value from options + * @member ns.widget.core.Drawer + */ + prototype.open = function (duration) { + var self = this, + options = self.options, + drawerClassList = self.element.classList, + drawerOverlay = self._ui.drawerOverlay; + + if (self._state !== STATE.OPENED) { + self._state = STATE.SETTLING; + self._settlingType = STATE.OPENED; + duration = duration !== undefined ? duration : options.duration; + if (drawerOverlay) { + drawerOverlay.style.visibility = "visible"; + } + drawerClassList.remove(classes.close); + drawerClassList.add(classes.open); + if (options.position === "left") { + self._translate(0, duration); + } else { + self._translate(window.innerWidth - options.width, duration); + } + } + }; + + /** + * Closes Drawer widget + * @requires mobile wearable + * + * + * @method close + * @param {Object} options This value is router options whether reverse or not. + * @param {number} [duration] Duration for closing, if is not set then method take value from options + * @member ns.widget.core.Drawer + */ + prototype.close = function (options, duration) { + var self = this, + reverse = options ? options.reverse : false, + selfOptions = self.options, + drawerClassList = self.element.classList; + + if (self._state !== STATE.CLOSED) { + if (!reverse && self._state === STATE.OPENED && !ns.getConfig("disableRouter")) { + // This method was fired by JS code or this widget. + history.back(); + return; + } + self._state = STATE.SETTLING; + self._settlingType = STATE.CLOSED; + duration = duration !== undefined ? duration : selfOptions.duration; + drawerClassList.remove(classes.open); + drawerClassList.add(classes.close); + if (selfOptions.position === "left") { + self._translate(-selfOptions.width, duration); + } else { + self._translate(window.innerWidth, duration); + } + } + }; + + /** + * Set Drawer drag handler. + * If developer use handler, drag event is bound at handler only. + * @method setDragHandler + * @param {HTMLElement} element + * @member ns.widget.core.Drawer + */ + prototype.setDragHandler = function (element) { + var self = this; + + self.options.dragEdge = 1; + unbindDragEvents(self, self._eventBoundElement); + bindDragEvents(self, element); + }; + + /** + * Transition Drawer widget. + * This method use only positive integer number. + * @method transition + * @param {number} position + * @member ns.widget.core.Drawer + */ + prototype.transition = function (position) { + var self = this, + options = self.options; + + if (options.position === "left") { + self._translate(-options.width + position, options.duration); + } else { + self._translate(options.width - position, options.duration); + } + self._translatedX = position; + }; + + /** + * Get state of Drawer widget. + */ + prototype.getState = function () { + return this._state; + }; + /** + * Destroys Drawer widget + * @method _destroy + * @member ns.widget.core.Drawer + * @protected + */ + prototype._destroy = function () { + var self = this, + ui = self._ui, + drawerOverlay = ui.drawerOverlay, + placeholder = ui.placeholder, + placeholderParent = placeholder.parentNode, + element = self.element; + + placeholderParent.insertBefore(element, placeholder); + placeholderParent.removeChild(placeholder); + + if (drawerOverlay) { + drawerOverlay.removeEventListener("vclick", self._onClickBound, false); + } + unbindDragEvents(self, self._eventBoundElement); + }; + + ns.widget.core.Drawer = Drawer; + + }(window.document, ns)); + +/*global window, ns, define */ +/*jslint nomen: true */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * #Route Drawer + * Support class for router to control drawer widget in profile Wearable. + * @class ns.router.route.drawer + * @author Hyeoncheol Choi + */ +(function () { + "use strict"; + var CoreDrawer = ns.widget.core.Drawer, + Router = ns.router.Router, + path = ns.util.path, + history = ns.history, + engine = ns.engine, + routeDrawer = {}, + drawerHashKey = "drawer=true", + drawerHashKeyReg = /([&|\?]drawer=true)/; + + routeDrawer.orderNumber = 1000; + /** + * Property containing default properties + * @property {Object} defaults + * @property {string} defaults.transition="none" + * @static + * @member ns.router.route.drawer + */ + routeDrawer.defaults = { + transition: "none" + }; + + /** + * Property defining selector for filtering only drawer elements + * @property {string} filter + * @member ns.router.route.drawer + * @static + */ + routeDrawer.filter = "." + CoreDrawer.classes.drawer; + + + /** + * Returns default route options used inside Router. + * But, drawer router has not options. + * @method option + * @static + * @member ns.router.route.drawer + * @return {null} + */ + routeDrawer.option = function () { + return null; + }; + + /** + * This method opens the drawer. + * @method open + * @param {HTMLElement} drawerElement + * @member ns.router.route.drawer + */ + routeDrawer.open = function (drawerElement) { + var drawer = engine.instanceWidget(drawerElement, "Drawer"); + + drawer.open(); + }; + + /** + * This method determines target drawer to open. + * @method find + * @param {string} absUrl Absolute path to opened drawer widget + * @member ns.router.route.drawer + * @return {?HTMLElement} drawerElement + */ + routeDrawer.find = function (absUrl) { + var dataUrl = path.convertUrlToDataUrl(absUrl), + activePage = Router.getInstance().getContainer().getActivePage(), + drawer; + + drawer = activePage.element.querySelector("#" + dataUrl); + + return drawer; + }; + + /** + * This method parses HTML and runs scripts from parsed code. + * But, drawer router doesn't need to that. + * @method parse + * @member ns.router.route.drawer + */ + routeDrawer.parse = function () { + return null; + }; + + /** + * This method sets active drawer and manages history. + * @method setActive + * @param {Object} activeDrawer + * @member ns.router.route.drawer + * @static + */ + routeDrawer.setActive = function (activeDrawer) { + var url, + pathLocation = path.getLocation(), + documentUrl = pathLocation.replace(drawerHashKeyReg, ""); + + this._activeDrawer = activeDrawer; + + if (activeDrawer) { + url = path.addHashSearchParams(documentUrl, drawerHashKey); + history.replace({}, "", url); + this.active = true; + } else if (pathLocation !== documentUrl) { + history.back(); + } + }; + + /** + * This method handles hash change. + * @method onHashChange + * @param {string} url + * @param {Object} options + * @param {string} prev Previous url string + * @static + * @member ns.router.route.drawer + * @return {null} + */ + routeDrawer.onHashChange = function (url, options, prev) { + var self = this, + activeDrawer = self._activeDrawer, + stateUrl = prev.stateUrl; + + if (activeDrawer && stateUrl.search(drawerHashKey) > 0 && url.search(drawerHashKey) < 0) { + activeDrawer.close(options); + this.active = false; + return true; + } + return false; + }; + + ns.router.route.drawer = routeDrawer; + + }()); + +/*global window, define, ns */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * #Anchor Highlight Utility + * + * Utility enables highlight on clickable components. + * @class ns.util.anchorHighlight + * @author Maciej Urbanski + * @author Damian Osipiuk + * @author Konrad Lipner + */ +(function (document, window, ns) { + "use strict"; + /* anchorHighlightController.js + To prevent performance regression when scrolling, + do not apply hover class in anchor. + Instead, this code checks scrolling for time threshold and + decide how to handle the color. + When scrolling with anchor, it checks flag and decide to highlight anchor. + While it helps to improve scroll performance, + it lowers responsiveness of the element for 50msec. + */ + + /** + * Touch start x + * @property {number} startX + * @member ns.util.anchorHighlight + * @private + * @static + */ + var startX = 0, + /** + * Touch start y + * @property {number} startY + * @member ns.util.anchorHighlight + * @private + * @static + */ + startY = 0, + /** + * Touch target element + * @property {HTMLElement} target + * @member ns.util.anchorHighlight + * @private + * @static + */ + classes = { + /** + * Class used to mark element as active + * @property {string} [classes.ACTIVE_LI="ui-li-active"] + * @member ns.util.anchorHighlight + * @private + * @static + */ + ACTIVE_LI: "ui-li-active", + /** + * Class used to mark button as active + * @property {string} [classes.ACTIVE_BTN="ui-btn-active"] + * @member ns.util.anchorHighlight + * @private + * @static + */ + ACTIVE_BTN: "ui-btn-active", + /** + * Class used to mark button as inactive + * @property {string} [classes.INACTIVE_BTN="ui-btn-inactive"] + * @member ns.util.anchorHighlight + * @private + * @static + */ + INACTIVE_BTN: "ui-btn-inactive", + /** + * Class used to select button + * @property {string} [classes.BUTTON="ui-btn"] btn + * @member ns.util.anchorHighlight + * @private + * @static + */ + BUTTON: "ui-btn", + /** + * Class used to select button in header (old notation) + * @property {string} [classes.HEADER_BUTTON="ui-header-btn"] btn + * @member ns.util.anchorHighlight + * @private + * @static + */ + HEADER_BUTTON: "ui-header-btn", + /** + * Class used to select anchor in tabbar widget + * @property {string} [classes.TABBAR_ANCHOR="ui-tabbar-anchor"] anchor + * @member ns.util.anchorHighlight + * @private + * @static + */ + TABBAR_ANCHOR: "ui-tabbar-anchor", + /** + * Class used to select navigation item + * @property {string} [classes.NAVIGATION_BUTTON="ui-navigation-item"] btn + * @member ns.util.anchorHighlight + * @private + * @static + */ + NAVIGATION_BUTTON: "ui-navigation-item" + }, + events = { + ACTIVE_LI: "anchorhighlightactiveli" + }, + /** + * Alias for class {@link ns.util.selectors} + * @property {Object} selectors + * @member ns.util.anchorHighlight + * @private + * @static + */ + selectors = ns.util.selectors, + /** + * Alias for class {@link ns.event} + * @property {Object} event + * @member ns.util.anchorHighlight + * @private + * @static + */ + eventUtil = ns.event, + + // cache function + abs = Math.abs, + + /** + * Get closest li element + * @method detectLiElement + * @param {HTMLElement} target + * @return {HTMLElement} + * @member ns.util.anchorHighlight + * @private + * @static + */ + detectLiElement = function (target) { + return selectors.getClosestByTag(target, "li"); + }, + + anchorHighlight = { + /** + * Object with default options + * @property {Object} options + * Threshold after which didScroll will be set + * @property {number} [options.scrollThreshold=10] + * Time to wait before adding activeClass + * @property {number} [options.addActiveClassDelay=50] + * Time to stay activeClass after touch end + * @property {number} [options.keepActiveClassDelay=100] + * @member ns.util.anchorHighlight + * @private + * @static + */ + options: { + scrollThreshold: 10, + addActiveClassDelay: 50, + keepActiveClassDelay: 100 + }, + _startTime: 0, + _startRemoveTime: 0, + // inform that touch was ended + _touchEnd: false, + _liTarget: null, + + /** + * Touch button target element + * @property {HTMLElement} buttonTarget + * @member ns.util.anchorHighlight + * @private + * @static + */ + _target: null, + /** + * Did page scrolled + * @property {boolean} didScroll + * @member ns.util.anchorHighlight + * @private + * @static + */ + _didScroll: false, + _buttonTarget: null, + // inform that animation of button's activation was ended + _activeAnimationFinished: false, + //cache function + _requestAnimationFrame: ns.util.windowRequestAnimationFrame + }, + + // cache function + slice = Array.prototype.slice; + + + /** + * Get closest highlightable element + * @method detectHighlightTarget + * @param {HTMLElement} target + * @return {HTMLElement} + * @member ns.util.anchorHighlight + * @private + * @static + */ + function detectHighlightTarget(target) { + return selectors.getClosestBySelector(target, "a, label"); + } + + /** + * Get closest button element + * @method detectLiElement + * @param {HTMLElement} target + * @return {HTMLElement} + * @member ns.util.anchorHighlight + * @private + * @static + */ + function detectBtnElement(target) { + return selectors.getClosestByClass(target, classes.BUTTON) || + selectors.getClosestByClass(target, classes.HEADER_BUTTON) || + selectors.getClosestByClass(target, classes.NAVIGATION_BUTTON) || + selectors.getClosestByClass(target, classes.TABBAR_ANCHOR); + } + + /** + * Clear active class on button + * @method clearBtnActiveClass + * @param {Event} event + * @member ns.util.anchorHighlight + * @private + * @static + */ + function clearBtnActiveClass(event) { + var target = event.target, + classList = target.classList; + // if this is callback of activate animation and + + if (classList.contains(classes.ACTIVE_BTN) && !classList.contains(classes.INACTIVE_BTN)) { + // set that animation was ended (used in touch end) + anchorHighlight._activeAnimationFinished = true; + + // if touch end previously + if (anchorHighlight._touchEnd || target !== anchorHighlight._buttonTarget) { + // start inactivate animation + classList.add(classes.INACTIVE_BTN); + } + } else { + //when target of animationend event is child of active element instead of active element + // itself + if (!classList.contains(classes.ACTIVE_BTN) && + !classList.contains(classes.INACTIVE_BTN)) { + target.parentNode.classList.remove(classes.ACTIVE_BTN); + target.parentNode.classList.remove(classes.INACTIVE_BTN); + } + // this is callback for inactive animation end + classList.remove(classes.INACTIVE_BTN); + classList.remove(classes.ACTIVE_BTN); + } + } + + /** + * Add inactive class on touch end + * @method addButtonInactiveClass + * @member ns.util.anchorHighlight + * @private + * @static + */ + function addButtonInactiveClass() { + if (anchorHighlight._buttonTarget) { + anchorHighlight._buttonTarget.classList.add(classes.INACTIVE_BTN); + } + } + + /** + * Add active class on touch end + * @method addButtonActiveClass + * @member ns.util.anchorHighlight + * @private + * @static + */ + function addButtonActiveClass() { + anchorHighlight._buttonTarget.classList.add(classes.ACTIVE_BTN); + anchorHighlight._activeAnimationFinished = false; + } + + /** + * Clear classes on page or popup hide + * @method hideClear + * @member ns.util.anchorHighlight + * @private + * @static + */ + function hideClear() { + var btnTarget = anchorHighlight._buttonTarget; + + if (btnTarget) { + btnTarget.classList.remove(classes.ACTIVE_BTN); + btnTarget.classList.remove(classes.INACTIVE_BTN); + } + if (anchorHighlight._target) { + anchorHighlight._target.classList.remove(classes.ACTIVE_LI); + } + } + + /** + * Add active class to touched element + * @method addActiveClass + * @member ns.util.anchorHighlight + * @private + * @static + */ + function addActiveClass() { + var btnTargetClassList, + dTime; + + if (anchorHighlight._startTime) { + dTime = Date.now() - anchorHighlight._startTime; + + if (dTime > anchorHighlight.options.addActiveClassDelay) { + anchorHighlight._startTime = 0; + anchorHighlight._buttonTarget = detectBtnElement(anchorHighlight._target); + anchorHighlight._target = detectHighlightTarget(anchorHighlight._target); + if (!anchorHighlight._didScroll) { + anchorHighlight._liTarget = anchorHighlight._detectLiElement(anchorHighlight._target); + if (anchorHighlight._liTarget) { + anchorHighlight._liTarget.classList.add(classes.ACTIVE_LI); + eventUtil.trigger(anchorHighlight._liTarget, events.ACTIVE_LI, {}); + } + anchorHighlight._liTarget = null; + if (anchorHighlight._buttonTarget) { + btnTargetClassList = anchorHighlight._buttonTarget.classList; + btnTargetClassList.remove(classes.ACTIVE_BTN); + btnTargetClassList.remove(classes.INACTIVE_BTN); + anchorHighlight._requestAnimationFrame(addButtonActiveClass); + } + } + } else { + anchorHighlight._requestAnimationFrame(addActiveClass); + } + } + } + + /** + * Get all active elements + * @method getActiveElements + * @return {Array} + * @member ns.util.anchorHighlight + * @private + * @static + */ + function getActiveElements() { + return slice.call(document.getElementsByClassName(classes.ACTIVE_LI)); + } + + /** + * Remove active class from current active objects + * @method clearActiveClass + * @member ns.util.anchorHighlight + * @private + * @static + */ + function clearActiveClass() { + var activeA = getActiveElements(), + activeALength = activeA.length, + i = 0; + + for (; i < activeALength; i++) { + activeA[i].classList.remove(classes.ACTIVE_LI); + } + } + + /** + * Remove active class from active elements + * @method removeActiveClass + * @member ns.util.anchorHighlight + * @private + * @static + */ + function removeActiveClassLoop() { + var dTime = Date.now() - anchorHighlight._startRemoveTime; + + if (dTime > anchorHighlight.options.keepActiveClassDelay) { + // after touchend + clearActiveClass(); + } else { + anchorHighlight._requestAnimationFrame(removeActiveClassLoop); + } + } + + /** + * Function invoked during touch move + * @method touchmoveHandler + * @param {Event} event + * @member ns.util.anchorHighlight + * @private + * @static + */ + function touchmoveHandler(event) { + var touch = event.touches[0], + scrollThreshold = anchorHighlight.options.scrollThreshold; + + // if move looks like scroll + if (!anchorHighlight._didScroll && + // if move is bigger then threshold + (abs(touch.clientX - startX) > scrollThreshold || + abs(touch.clientY - startY) > scrollThreshold)) { + anchorHighlight._startTime = 0; + // we clear active classes + anchorHighlight._requestAnimationFrame(clearActiveClass); + anchorHighlight._didScroll = true; + } + } + + /** + * Function invoked after touch start + * @method touchstartHandler + * @param {Event} event + * @member ns.util.anchorHighlight + * @private + * @static + */ + function touchstartHandler(event) { + var touches = event.touches, + touch; + + if (touches.length === 1) { + touch = touches[0]; + anchorHighlight._didScroll = false; + startX = touch.clientX; + startY = touch.clientY; + anchorHighlight._target = event.target; + anchorHighlight._startTime = Date.now(); + anchorHighlight._startRemoveTime = 0; + anchorHighlight._requestAnimationFrame(addActiveClass); + anchorHighlight._touchEnd = false; + } + } + + + /** + * Function invoked after touch + * @method touchendHandler + * @param {Event} event + * @member ns.util.anchorHighlight + * @private + * @static + */ + function touchendHandler(event) { + anchorHighlight._startRemoveTime = event.timeStamp; + + if (event.touches.length === 0) { + if (!anchorHighlight._didScroll) { + anchorHighlight._startTime = 0; + anchorHighlight._requestAnimationFrame(removeActiveClassLoop); + } + // if we finished activate animation then start inactive animation + if (anchorHighlight._activeAnimationFinished) { + anchorHighlight._requestAnimationFrame(addButtonInactiveClass); + } + anchorHighlight._didScroll = false; + anchorHighlight._touchEnd = true; + } + } + + /** + * Function invoked after visibilitychange event + * @method checkPageVisibility + * @member ns.util.anchorHighlight + * @private + * @static + */ + function checkPageVisibility() { + /* istanbul ignore if */ + if (document.visibilityState === "hidden") { + anchorHighlight._removeActiveClassLoop(); + } + } + + ns.util.anchorHighlight = anchorHighlight; + anchorHighlight.enable = enable; + anchorHighlight.disable = disable; + anchorHighlight._clearActiveClass = clearActiveClass; + anchorHighlight._detectHighlightTarget = detectHighlightTarget; + anchorHighlight._detectBtnElement = detectBtnElement; + anchorHighlight._clearBtnActiveClass = clearBtnActiveClass; + anchorHighlight._removeActiveClassLoop = removeActiveClassLoop; + anchorHighlight._addButtonInactiveClass = addButtonInactiveClass; + anchorHighlight._addButtonActiveClass = addButtonActiveClass; + anchorHighlight._hideClear = hideClear; + anchorHighlight._addActiveClass = addActiveClass; + anchorHighlight._detectLiElement = detectLiElement; + anchorHighlight._touchmoveHandler = touchmoveHandler; + anchorHighlight._touchendHandler = touchendHandler; + anchorHighlight._touchstartHandler = touchstartHandler; + anchorHighlight._checkPageVisibility = checkPageVisibility; + anchorHighlight._hideClear = hideClear; + anchorHighlight._clearBtnActiveClass = clearBtnActiveClass; + + /** + * Bind events to document + * @method enable + * @member ns.util.anchorHighlight + * @static + */ + function enable() { + document.addEventListener("touchstart", anchorHighlight._touchstartHandler, false); + document.addEventListener("touchend", anchorHighlight._touchendHandler, false); + document.addEventListener("touchmove", anchorHighlight._touchmoveHandler, false); + + document.addEventListener("visibilitychange", anchorHighlight._checkPageVisibility, false); + document.addEventListener("pagehide", anchorHighlight._hideClear, false); + document.addEventListener("popuphide", anchorHighlight._hideClear, false); + document.addEventListener("animationend", anchorHighlight._clearBtnActiveClass, false); + document.addEventListener("animationEnd", anchorHighlight._clearBtnActiveClass, false); + document.addEventListener("webkitAnimationEnd", anchorHighlight._clearBtnActiveClass, + false); + } + + /** + * Unbinds events from document. + * @method disable + * @member ns.util.anchorHighlight + * @static + */ + function disable() { + document.removeEventListener("touchstart", anchorHighlight._touchstartHandler, false); + document.removeEventListener("touchend", anchorHighlight._touchendHandler, false); + document.removeEventListener("touchmove", anchorHighlight._touchmoveHandler, false); + + document.removeEventListener("visibilitychange", anchorHighlight._checkPageVisibility, + false); + document.removeEventListener("pagehide", anchorHighlight._hideClear, false); + document.removeEventListener("popuphide", anchorHighlight._hideClear, false); + document.removeEventListener("animationend", anchorHighlight._clearBtnActiveClass, false); + document.removeEventListener("animationEnd", anchorHighlight._clearBtnActiveClass, false); + document.removeEventListener("webkitAnimationEnd", anchorHighlight._clearBtnActiveClass, + false); + } + + enable(); + + }(document, window, ns)); + +/*global window, ns, define */ +(function (window, document, ns) { + "use strict"; + var PI = Math.PI, + cos = Math.cos, + sin = Math.sin, + SVGNS = "http://www.w3.org/2000/svg", + objectUtils = ns.util.object, + classes = { + polar: "ui-polar", + animated: "ui-animated" + }, + defaultsArc = { + x: 180, + y: 180, + r: 170, + arcStart: 0, + arcEnd: 90, + width: 5, + color: "black", + animation: false, + linecap: "butt", + referenceDegree: 0 + }, + defaultsRadius = { + x: 180, + y: 180, + r: 170, + degrees: 0, + length: 180, + direction: "in", + width: 5, + color: "black" + }, + defaultsText = { + x: 180, + y: 180, + text: "Text", + position: "middle", + color: "white" + }, + defaultsCircle = { + x: 180, + y: 180, + r: 170, + color: "white" + }, + polar; + + /** + * Calculate polar coords to cartesian + * @param {number} centerX + * @param {number} centerY + * @param {number} radius + * @param {number} angleInDegrees + * @return {{x: number, y: number}} + */ + function polarToCartesian(centerX, centerY, radius, angleInDegrees) { + var angleInRadians = angleInDegrees * PI / 180.0; + + return { + x: centerX + (radius * sin(angleInRadians)), + y: centerY - (radius * cos(angleInRadians)) + }; + } + + /** + * Create description of path for arc + * @param {number} x + * @param {number} y + * @param {number} radius + * @param {number} startAngle Angle in degrees where arc starts + * @param {number} endAngle Angle in degrees where arc ends + * @return {string} + */ + function describeArc(x, y, radius, startAngle, endAngle) { + var start = polarToCartesian(x, y, radius, endAngle), + end = polarToCartesian(x, y, radius, startAngle), + arcSweep = endAngle - startAngle <= 180 ? "0" : "1", + clockWise = 0; + + return [ + "M", start.x, start.y, + "A", radius, radius, 0, arcSweep, clockWise, end.x, end.y + ].join(" "); + } + + function addPath(svg, options) { + var path = document.createElementNS(SVGNS, "path"); + + path.setAttribute("class", options.classes); + path.setAttribute("fill", "none"); + path.setAttribute("stroke", options.color); + path.setAttribute("stroke-width", options.width); + path.setAttribute("d", describeArc(options.x, options.y, options.r, + options.referenceDegree + options.arcStart, options.referenceDegree + options.arcEnd)); + path.setAttribute("data-initial-degree", options.referenceDegree); + path.setAttribute("stroke-linecap", options.linecap); + + svg.appendChild(path); + } + + function addAnimation(element, options) { + var style = element.style, + value = options.x + "px " + options.y + "px", + degrees = (options.referenceDegree + options.arcStart) || options.degrees; + + // phantom not support classList on SVG + if (element.classList) { + // add class for transition + element.classList.add(classes.animated); + } + + // set transform + style.webkitTransformOrigin = value; + style.mozTransformOrigin = value; + style.transformOrigin = value; + + value = "rotate(" + degrees + "deg)"; + style.webkitTransform = value; + style.mozTransform = value; + style.transform = value; + } + + function addRadius(svg, options) { + var line = document.createElementNS(SVGNS, "line"), + positionStart, + positionEnd; + + line.setAttribute("class", options.classes); + line.setAttribute("stroke", options.color); + line.setAttribute("stroke-width", options.width); + if (options.direction === "out") { + positionStart = polarToCartesian(options.x, options.y, options.r, options.degrees); + positionEnd = polarToCartesian(options.x, options.y, options.r - options.length, + options.degrees); + } else { + positionStart = polarToCartesian(options.x, options.y, options.r - options.length, + options.degrees); + positionEnd = polarToCartesian(options.x, options.y, options.r, options.degrees); + } + line.setAttribute("x1", positionStart.x); + line.setAttribute("y1", positionStart.y); + line.setAttribute("x2", positionEnd.x); + line.setAttribute("y2", positionEnd.y); + + svg.appendChild(line); + return line; + } + + function addText(svg, options) { + var text = document.createElementNS(SVGNS, "text"); + + text.setAttribute("x", options.x); + text.setAttribute("y", options.y); + text.setAttribute("text-anchor", options.position); + text.setAttribute("fill", options.color); + text.setAttribute("transform", options.transform); + text.textContent = options.text; + + svg.appendChild(text); + } + + function addCircle(svg, options) { + var circle = document.createElementNS(SVGNS, "circle"); + + circle.setAttribute("stroke", options.color); + circle.setAttribute("stroke-width", options.width); + circle.setAttribute("cx", options.x); + circle.setAttribute("cy", options.y); + circle.setAttribute("r", options.r); + circle.setAttribute("fill", options.fill); + + svg.appendChild(circle); + return circle; + } + + function updatePathPosition(path, options) { + var reference; + + if (options.animation) { + addAnimation(path, options); + } else { + if (path) { + reference = parseInt(path.getAttribute("data-initial-degree"), 10) || options.referenceDegree; + path.setAttribute("data-initial-degree", reference); + path.setAttribute("d", describeArc(options.x, options.y, options.r, + reference + options.arcStart, reference + options.arcEnd)); + } + } + } + + function updateLinePosition(line, options) { + var positionStart, + positionEnd; + + if (options.animation) { + addAnimation(line, options); + } else { + if (line) { + positionStart = polarToCartesian(options.x, options.y, options.r, options.degrees); + positionEnd = polarToCartesian(options.x, options.y, options.r - options.length, options.degrees); + + line.setAttribute("x1", positionStart.x); + line.setAttribute("y1", positionStart.y); + line.setAttribute("x2", positionEnd.x); + line.setAttribute("y2", positionEnd.y); + } + } + } + + polar = { + default: { + arc: defaultsArc, + radius: defaultsRadius, + text: defaultsText + }, + classes: classes, + + polarToCartesian: polarToCartesian, + + /** + * creates SVG element + * @method createSVG + * @member ns.util.polar + * @param {HTMLElement} element + * @return {SVGElement} + * @static + */ + createSVG: function (element) { + var svg = document.createElementNS(SVGNS, "svg"); + + // phantom not support classList on SVG + if (svg.classList) { + // add class to svg element + svg.classList.add(classes.polar); + } + // if element is set, add svg as child node + if (element) { + element.appendChild(svg); + } + return svg; + }, + + /** + * draw arc on the svg element + * @method addArc + * @member ns.util.polar + * @param {SVGElement} svg + * @param {Object} options + * @return {SVGElement} + * @static + */ + addArc: function (svg, options) { + // read or create new svg + svg = svg || this.createSVG(); + // set options + options = objectUtils.merge({}, defaultsArc, options || {}); + // add path with arc + addPath(svg, options); + + return svg; + }, + + /** + * draw radius on the svg element + * @method addRadius + * @member ns.util.polar + * @param {SVGElement} svg + * @param {Object} options + * @return {SVGElement} + * @static + */ + addRadius: function (svg, options) { + // read or create new svg + svg = svg || this.createSVG(); + // add path with radius + options = objectUtils.merge({}, defaultsRadius, options || {}); + return addRadius(svg, options); + }, + + /** + * draw text on the svg element + * @method addText + * @member ns.util.polar + * @param {SVGElement} svg + * @param {Object} options + * @return {SVGElement} + * @static + */ + addText: function (svg, options) { + // read or create new svg + svg = svg || this.createSVG(); + // add path with radius + options = objectUtils.merge({}, defaultsText, options || {}); + addText(svg, options); + + return svg; + }, + + /** + * updatePosition for path or for line drawings in svg + * @method updatePosition + * @member ns.util.polar + * @param {SVGElement} svg + * @param {string} selector + * @param {Object} options + * @static + */ + updatePosition: function (svg, selector, options) { + var path = svg && svg.querySelector("path" + selector), + line; + + if (path) { + // set options + options = objectUtils.merge({}, defaultsArc, options || {}); + updatePathPosition(path, options); + } else { + line = svg && svg.querySelector("line" + selector); + if (line) { + updateLinePosition(line, options); + } + } + }, + + /** + * draw circle on the svg element + * @method addCircle + * @member ns.util.polar + * @param {SVGElement} svg + * @param {Object} options + * @return {SVGElement} + * @static + */ + addCircle: function (svg, options) { + var self = this; + + // read or create svg + svg = svg || self.createSVG(); + + options = objectUtils.merge({}, defaultsCircle, options || {}); + addCircle(svg, options); + + return svg; + } + }; + + ns.util.polar = polar; + }(window, window.document, ns)); + +/* global requestAnimationFrame, define, ns, Math */ +/** + * # JS base scrolling tool + * + * This enable fast scrolling on element + * + * @class ns.util.scrolling + */ +(function (document, window, ns) { + "use strict"; + var eventUtil = ns.event, + polarUtil = ns.util.polar, + classes = { + circular: "scrolling-circular", + direction: "scrolling-direction", + scrollbar: "scrolling-scrollbar", + path: "scrolling-path", + thumb: "scrolling-scrollthumb", + fadeIn: "fade-in" + }, + bounceBack = false, + EVENTS = { + SCROLL_BEFORE_START: "beforeScrollStart", + SCROLL_START: "scrollStart", + SCROLL_END: "scrollEnd", + SCROLL_FLICK: "flick", + SCROLL: "scroll" + }, + // 1px line space from screen edge + RADIUS = 174, + // position when was last touch start + startPosition = 0, + // current state of scroll position + scrollPosition = 0, + lastScrollPosition = 0, + moveToPosition = 0, + lastRenderedPosition = 0, + lastTime = Date.now(), + elementStyle = null, + maxScrollPosition = 0, + // scrolling element + scrollingElement = null, + childElement = null, + // cache of previous overflow style to revert after disable + previousOverflow = "", + // cache abs function + abs = Math.abs, + // inform that is touched + isTouch = false, + isScrollableTarget = false, + // direction of scrolling, 0 - mean Y, 1 - mean X + direction = 0, + // cache of round function + round = Math.round, + // cache max function + max = Math.max, + min = Math.min, + + // Circular scrollbar config + CIRCULAR_SCROLL_BAR_SIZE = 60, // degrees + CIRCULAR_SCROLL_MIN_THUMB_SIZE = 6, + + // Scrollbar is placed after scrolled element + // that's why normal css values cannot be applied + // margin needs to be subtracted from position + SCROLL_MARGIN = 11, + + // ScrollBar variables + scrollBar = null, + scrollThumb = null, + scrollBarPosition = 0, + maxScrollBarPosition = 0, + circularScrollBar = ns.support.shape.circle, + circularScrollThumbSize = CIRCULAR_SCROLL_MIN_THUMB_SIZE, + svgScrollBar = null, + scrollBarTimeout = null, + fromAPI = false, + virtualMode = false, + snapSize = null, + requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame; + + /** + * Shows scrollbar using fadeIn class and sets timeout to hide it when not used + */ + function fadeInScrollBar() { + if (scrollBar) { + clearTimeout(scrollBarTimeout); + scrollBar.classList.add(classes.fadeIn); + + scrollBarTimeout = setTimeout(function () { + if (scrollBar) { + scrollBar.classList.remove(classes.fadeIn); + } + }, 2000); + } + } + + /** + * Check that current target is inside scrolling element + * @param {HTMLElement} target + * @return {boolean} + */ + function detectTarget(target) { + while (target && target !== document) { + if (target === scrollingElement) { + return true; + } + target = target.parentElement; + } + return false; + } + + /** + * Handler for touchstart event + * @param {Event} event + */ + function touchStart(event) { + var touches = event.touches, + touch = touches[0]; + + isScrollableTarget = detectTarget(event.target); + // is is only one touch + if (isScrollableTarget && touches.length === 1) { + // save current touch point + startPosition = direction ? touch.clientX : touch.clientY; + // save current time for calculate acceleration on touchend + lastTime = Date.now(); + eventUtil.trigger(scrollingElement, EVENTS.SCROLL_BEFORE_START, { + scrollLeft: direction ? -scrollPosition : 0, + scrollTop: direction ? 0 : -scrollPosition, + fromAPI: fromAPI + }); + } + } + + function touchMoveMain(event) { + var touches = event.touches, + touch = touches[0], + // get current position in correct direction + clientPosition = direction ? touch.clientX : touch.clientY, + scrollLeft = 0, + scrollTop = 0; + + fromAPI = false; + // calculate difference between touch start and current position + lastScrollPosition = clientPosition - startPosition; + if (!bounceBack) { + // normalize value to be in bound [0, maxScroll] + if (scrollPosition + lastScrollPosition > 0) { + lastScrollPosition = -scrollPosition; + } + if (scrollPosition + lastScrollPosition < -maxScrollPosition) { + lastScrollPosition = -maxScrollPosition - scrollPosition; + } + } + + if (direction) { + scrollLeft = -(scrollPosition + lastScrollPosition); + } else { + scrollTop = -(scrollPosition + lastScrollPosition); + } + + // trigger event scroll start if it is the first touch move + if (!isTouch) { + eventUtil.trigger(scrollingElement, EVENTS.SCROLL_START, { + scrollLeft: scrollLeft, + scrollTop: scrollTop, + fromAPI: fromAPI + }); + } + + // trigger event scroll + eventUtil.trigger(scrollingElement, EVENTS.SCROLL, { + scrollLeft: scrollLeft, + scrollTop: scrollTop, + inBounds: (scrollPosition + lastScrollPosition >= -maxScrollPosition) && + (scrollPosition + lastScrollPosition <= 0), + fromAPI: fromAPI + }); + fadeInScrollBar(); + } + + /** + * Handler for touchmove event + * @param {Event} event + */ + function touchMove(event) { + var touches = event.touches; + + // if touch start was on scrolled element + if (isScrollableTarget) { + // if is only one touch + if (touches.length === 1) { + touchMoveMain(event); + } + // if this is first touch move + if (!isTouch) { + // we need start request loop + isTouch = true; + } + requestAnimationFrame(render); + } + } + + function touchEndCalculateSpeed(inBounds) { + var diffTime = Date.now() - lastTime; + + if (inBounds && abs(lastScrollPosition / diffTime) > 1) { + // if it was fast move, we start animation of scrolling after touch end + moveToPosition = max(min(round(scrollPosition + 1000 * lastScrollPosition / diffTime), + 0), -maxScrollPosition); + if (snapSize) { + moveToPosition = snapSize * round(moveToPosition / snapSize); + } + if (abs(lastScrollPosition / diffTime) > 1) { + eventUtil.trigger(scrollingElement, EVENTS.SCROLL_FLICK, { + scrollLeft: direction ? -moveToPosition : 0, + scrollTop: direction ? 0 : -moveToPosition, + fromAPI: fromAPI + }); + } + requestAnimationFrame(moveTo); + } else { + // touch move was slow + if (snapSize) { + moveToPosition = snapSize * round(scrollPosition / snapSize); + requestAnimationFrame(moveTo); + } + isTouch = false; + } + } + + function touchEndCalculatePosition(inBounds) { + if (bounceBack) { + if (!inBounds) { + // if it was fast move, we start animation of scrolling after touch end + if (scrollPosition > 0) { + moveToPosition = 0; + } else { + moveToPosition = -maxScrollPosition; + } + requestAnimationFrame(moveTo); + } + } else { + // normalize value to be in bound [0, maxScroll] + if (scrollPosition < -maxScrollPosition) { + scrollPosition = -maxScrollPosition; + } + if (scrollPosition > 0) { + scrollPosition = 0; + } + } + } + + function touchEndTriggerEvents(details) { + eventUtil.trigger(scrollingElement, EVENTS.SCROLL, details); + eventUtil.trigger(scrollingElement, EVENTS.SCROLL_END, details); + } + + /** + * Handler for touchend event + */ + function touchEnd() { + var inBounds, + scrollLeft = 0, + scrollTop = 0; + // only if the event touchmove was noticed before + + if (isTouch) { + // update state of scrolling + scrollPosition += lastScrollPosition; + inBounds = (scrollPosition >= -maxScrollPosition) && (scrollPosition <= 0); + + // calculate speed of touch move + touchEndCalculateSpeed(inBounds); + touchEndCalculatePosition(inBounds); + + if (direction) { + scrollLeft = -(scrollPosition); + } else { + scrollTop = -(scrollPosition); + } + + lastScrollPosition = 0; + // trigger event scroll + touchEndTriggerEvents({ + scrollLeft: scrollLeft, + scrollTop: scrollTop, + inBounds: inBounds, + fromAPI: fromAPI + }); + fadeInScrollBar(); + // we stop scrolling + isScrollableTarget = false; + requestAnimationFrame(render); + } + } + + /** + * Handler for rotary event + * @param {Event} event + */ + function rotary(event) { + var eventDirection = event.detail && event.detail.direction; + + // update position by snapSize + if (eventDirection === "CW") { + moveToPosition -= snapSize || 50; + } else { + moveToPosition += snapSize || 50; + } + if (snapSize) { + moveToPosition = snapSize * round(moveToPosition / snapSize); + } + if (moveToPosition < -maxScrollPosition) { + moveToPosition = -maxScrollPosition; + } + if (moveToPosition > 0) { + moveToPosition = 0; + } + requestAnimationFrame(moveTo); + requestAnimationFrame(render); + eventUtil.trigger(scrollingElement, EVENTS.SCROLL_START, { + scrollLeft: direction ? -(moveToPosition) : 0, + scrollTop: direction ? 0 : -(moveToPosition), + fromAPI: false + }); + event.stopImmediatePropagation(); + } + + function moveToCalculatePosition() { + var diffPosition = moveToPosition - scrollPosition, + // get absolute value + absDiffPosition = abs(diffPosition); + + if (absDiffPosition > 10) { + // we move 10% of difference + scrollPosition = round(scrollPosition + diffPosition / 10); + requestAnimationFrame(moveTo); + } else if (absDiffPosition > 2) { + // else if is difference < 10 then we move 50% + scrollPosition = round(scrollPosition + diffPosition / 2); + requestAnimationFrame(moveTo); + } else { + // if difference is <=2 then we move to end value and finish loop + scrollPosition = moveToPosition; + } + if (!bounceBack) { + // normalize scroll value + if (scrollPosition < -maxScrollPosition) { + scrollPosition = -maxScrollPosition; + } + if (scrollPosition > 0) { + scrollPosition = 0; + } + } + } + + /** + * Loop function to calculate state in animation after touchend + */ + function moveTo() { + // calculate difference between current position and expected scroll end + var scrollLeft = 0, + scrollTop = 0; + + // if difference is big + if (scrollingElement) { + moveToCalculatePosition(); + + if (direction) { + scrollLeft = -scrollPosition; + } else { + scrollTop = -scrollPosition; + } + // trigger event scroll + eventUtil.trigger(scrollingElement, EVENTS.SCROLL, { + scrollLeft: scrollLeft, + scrollTop: scrollTop, + inBounds: (scrollPosition >= -maxScrollPosition) && (scrollPosition <= 0), + fromAPI: fromAPI + }); + if (!isTouch) { + eventUtil.trigger(scrollingElement, EVENTS.SCROLL_END, { + scrollLeft: scrollLeft, + scrollTop: scrollTop, + fromAPI: fromAPI + }); + } + + fadeInScrollBar(); + } + } + + function renderScrollbar() { + if (circularScrollBar) { + polarUtil.updatePosition(svgScrollBar, "." + classes.thumb, { + arcStart: scrollBarPosition, + arcEnd: scrollBarPosition + circularScrollThumbSize, + r: RADIUS + }); + } else { + if (scrollThumb) { + if (direction) { + scrollThumb.style.transform = "translate(" + scrollBarPosition + "px, 0)"; + } else { + scrollThumb.style.transform = "translate(0, " + scrollBarPosition + "px)"; + } + } + } + } + /** + * Render loop on request animation frame + */ + function render() { + // calculate ne position of scrolling as sum of last scrolling state + move + var newRenderedPosition = scrollPosition + lastScrollPosition; + // is position was changed + + if (newRenderedPosition !== lastRenderedPosition) { + // we update styles + lastRenderedPosition = newRenderedPosition; + if (-newRenderedPosition < maxScrollPosition) { + scrollBarPosition = -newRenderedPosition / maxScrollPosition * maxScrollBarPosition; + } else { + scrollBarPosition = maxScrollBarPosition; + } + if (scrollBarPosition < 0) { + scrollBarPosition = 0; + } + + if (!virtualMode && elementStyle) { + elementStyle.transform = direction ? + "translate(" + lastRenderedPosition + "px, 0)" : + "translate(0, " + lastRenderedPosition + "px)"; + } + + renderScrollbar(); + requestAnimationFrame(render); + } + } + + function initPosition() { + // init internal variables + startPosition = 0; + scrollPosition = 0; + scrollBarPosition = 0; + lastScrollPosition = 0; + moveToPosition = 0; + lastRenderedPosition = 0; + lastTime = Date.now(); + } + + /** + * Enable JS scrolling on element + * @method enable + * @param {HTMLElement} element element for scrolling + * @param {"x"|"y"} [setDirection="y"] direction of scrolling + * @param {boolean} setVirtualMode if is set to true then send event without scroll element + * @member ns.util.scrolling + */ + function enable(element, setDirection, setVirtualMode) { + var parentRectangle, + contentRectangle; + + virtualMode = setVirtualMode; + bounceBack = false; + snapSize = false; + + if (scrollingElement) { + ns.warn("Scrolling exist on another element, first call disable method"); + } else { + // detect direction + direction = (setDirection === "x") ? 1 : 0; + + // we are creating a container to position transform + childElement = document.createElement("div"); + // ... and appending all children to it + while (element.firstElementChild) { + childElement.appendChild(element.firstElementChild); + } + element.appendChild(childElement); + + // setting scrolling element + scrollingElement = element; + // calculate maxScroll + parentRectangle = element.getBoundingClientRect(); + contentRectangle = childElement.getBoundingClientRect(); + + // Max scroll position is determined by size of the content - clip window size + if (direction) { + maxScrollPosition = round(contentRectangle.width - parentRectangle.width); + } else { + maxScrollPosition = round(contentRectangle.height - parentRectangle.height); + } + + // cache style element + elementStyle = childElement.style; + initPosition(); + // cache current overflow value to restore in disable + previousOverflow = window.getComputedStyle(element).getPropertyValue("overflow"); + // set overflow hidden + element.style.overflow = "hidden"; + // add event listeners + document.addEventListener("touchstart", touchStart, false); + document.addEventListener("touchmove", touchMove, false); + document.addEventListener("touchend", touchEnd, false); + window.addEventListener("rotarydetent", rotary, true); + } + } + + /** + * @method disable + * @member ns.util.scrolling + */ + function disable() { + disableScrollBar(); + + // clear event listeners + document.removeEventListener("touchstart", touchStart, false); + document.removeEventListener("touchmove", touchMove, false); + document.removeEventListener("touchend", touchEnd, false); + window.removeEventListener("rotarydetent", rotary, true); + + // after changed page and removed it this element can not exists + if (scrollingElement) { + scrollingElement.style.overflow = previousOverflow; + } + + elementStyle = null; + scrollingElement = null; + childElement = null; + svgScrollBar = null; + } + + function enableScrollBarCircular() { + var arcStartPoint = direction ? 0 : 90; + + scrollBar.classList.add(classes.circular); + svgScrollBar = polarUtil.createSVG(); + + // create background + polarUtil.addArc(svgScrollBar, { + arcStart: arcStartPoint - (CIRCULAR_SCROLL_BAR_SIZE / 2), + arcEnd: arcStartPoint + (CIRCULAR_SCROLL_BAR_SIZE / 2), + classes: classes.path, + width: 10, + r: RADIUS, + linecap: "round" + }); + // create thumb + polarUtil.addArc(svgScrollBar, { + referenceDegree: arcStartPoint - (CIRCULAR_SCROLL_BAR_SIZE / 2), + arcStart: 0, + arcEnd: circularScrollThumbSize, + classes: classes.thumb, + width: 10, + r: RADIUS, + linecap: "round" + }); + + scrollBar.appendChild(svgScrollBar); + scrollingElement.parentElement.insertBefore(scrollBar, scrollingElement.nextSibling); + } + + function enableScrollBarRectangular(scrollBarStyle) { + var boundingRect, + childElementRect, + scrollThumbStyle, + scrollBarWidth = 0, + scrollBarHeight = 0; + + scrollBar.classList.add(classes.direction + "-" + (direction ? "x" : "y")); + + scrollThumb = document.createElement("div"); + scrollThumbStyle = scrollThumb.style; + scrollThumb.classList.add(classes.thumb); + + boundingRect = scrollingElement.getBoundingClientRect(); + childElementRect = childElement.getBoundingClientRect(); + + if (direction) { + scrollBarWidth = (boundingRect.width - (2 * SCROLL_MARGIN)); + + scrollBarStyle.width = scrollBarWidth + "px"; + scrollBarStyle.left = (boundingRect.left + SCROLL_MARGIN) + "px"; + scrollThumbStyle.transform = "translate3d(" + scrollBarPosition + "px,0,0)"; + // Calculate size of the thumb (only useful when enabling after content has size > 0) + scrollThumbStyle.width = (scrollBarWidth / childElementRect.width * scrollBarWidth) + + "px"; + } else { + scrollBarHeight = (boundingRect.height - (2 * SCROLL_MARGIN)); + + scrollBarStyle.height = scrollBarHeight + "px"; + scrollBarStyle.top = (boundingRect.top + SCROLL_MARGIN) + "px"; + scrollThumbStyle.transform = "translate3d(0," + scrollBarPosition + "px,0)"; + // Calculate size of the thumb (only useful when enabling after content has size > 0) + scrollThumbStyle.height = (scrollBarHeight / childElementRect.height * scrollBarHeight) + + "px"; + } + + scrollBar.appendChild(scrollThumb); + scrollingElement.parentElement.insertBefore(scrollBar, scrollingElement.nextSibling); + + // Get max scrollbar position after appending + if (direction) { + maxScrollBarPosition = scrollBarWidth - scrollThumb.getBoundingClientRect().width; + } else { + maxScrollBarPosition = scrollBarHeight - scrollThumb.getBoundingClientRect().height; + } + } + + function enableScrollBar() { + scrollBar = document.createElement("div"); + scrollBar.classList.add(classes.scrollbar); + + if (circularScrollBar) { + enableScrollBarCircular(); + } else { + enableScrollBarRectangular(scrollBar.style); + } + } + + function disableScrollBar() { + if (scrollBar) { + scrollBar.parentElement.removeChild(scrollBar); + scrollBar = null; + scrollThumb = null; + } + } + + /** + * Scroll to give position + * @method scrollTo + * @param {number} value + * @member ns.util.scrolling + */ + function scrollTo(value) { + moveToPosition = value; + fromAPI = true; + eventUtil.trigger(scrollingElement, EVENTS.SCROLL_BEFORE_START, { + scrollLeft: direction ? -scrollPosition : 0, + scrollTop: direction ? 0 : -scrollPosition, + fromAPI: fromAPI + }); + requestAnimationFrame(moveTo); + render(); + } + + /** + * Return scroll position + * @method getScrollPosition + * @member ns.util.scrolling + */ + function getScrollPosition() { + return -scrollPosition; + } + + /** + * Return max scroll position + * @method getMaxScroll + * @member ns.util.scrolling + */ + function getMaxScroll() { + return maxScrollPosition; + } + + ns.util.scrolling = { + getScrollPosition: getScrollPosition, + enable: enable, + disable: disable, + enableScrollBar: enableScrollBar, + disableScrollBar: disableScrollBar, + scrollTo: scrollTo, + /** + * Return true is given element is current scrolling element + * @method isElement + * @param {HTMLElement} element element to check + * @return {boolean} + * @member ns.util.scrolling + */ + isElement: function (element) { + return scrollingElement === element; + }, + + /** + * Update max scrolling position + * @method setMaxScroll + * @param {number} maxValue + * @member ns.util.scrolling + */ + setMaxScroll: function (maxValue) { + var boundingRect = scrollingElement.getBoundingClientRect(), + directionDimension = direction ? "width" : "height", + directionSize = boundingRect[directionDimension], + tempMaxPosition = max(maxValue - directionSize, 0); + + // Change size of thumb only when necessary + if (tempMaxPosition !== maxScrollPosition) { + maxScrollPosition = tempMaxPosition || Number.POSITIVE_INFINITY; + if (scrollBar) { + if (circularScrollBar) { + // Calculate new thumb size based on max scrollbar size + circularScrollThumbSize = max((directionSize / (maxScrollPosition + directionSize)) * + CIRCULAR_SCROLL_BAR_SIZE, CIRCULAR_SCROLL_MIN_THUMB_SIZE); + maxScrollBarPosition = CIRCULAR_SCROLL_BAR_SIZE - circularScrollThumbSize; + polarUtil.updatePosition(svgScrollBar, "." + classes.thumb, { + arcStart: scrollBarPosition, + arcEnd: scrollBarPosition + circularScrollThumbSize, + r: RADIUS + }); + } else { + directionSize -= 2 * SCROLL_MARGIN; + scrollThumb.style[directionDimension] = + (directionSize / (maxScrollPosition + directionSize) * directionSize) + "px"; + // Cannot use direct value from style here because CSS may override the minimum + // size of thumb here + maxScrollBarPosition = directionSize - + scrollThumb.getBoundingClientRect()[directionDimension]; + } + } + } + }, + getMaxScroll: getMaxScroll, + setSnapSize: function (setSnapSize) { + snapSize = setSnapSize; + if (snapSize) { + maxScrollPosition = snapSize * round(maxScrollPosition / snapSize); + } + }, + setBounceBack: function (setBounceBack) { + bounceBack = setBounceBack; + } + }; + + }(document, window, ns)); + +/* global define, ns */ +/** + * #Scrolling by rotary event + * + * Tool to enable scrolling by rotary event. + * + * ##How tu use + * + * @example + *
+ * Long content to scroll + *
+ * + * + * + * @class ns.util.rotaryScrolling + */ +(function (document, window, ns) { + "use strict"; + var rotaryScrolling = {}, + element = null, + /** + * Scroll step + * @type {number} + */ + scrollStep = 40; + + /** + * Handler for rotary event + * @param {Event} event Event object + */ + function rotaryDetentHandler(event) { + if (event.detail.direction === "CW") { + element.scrollTop += scrollStep; + } else { + element.scrollTop -= scrollStep; + } + } + + /** + * Enable Rotary event scrolling + * @param {HTMLElement} newElement Base element to scroll + * @param {number} newScrollDiff Value of scroll step + * @method enable + * @memberof ns.util.rotaryScrolling + */ + function enable(newElement, newScrollDiff) { + element = newElement; + if (newScrollDiff) { + scrollStep = newScrollDiff; + } + document.addEventListener("rotarydetent", rotaryDetentHandler); + } + + /** + * Disable rotary event scrolling + * @method disable + * @memberof ns.util.rotaryScrolling + */ + function disable() { + scrollStep = 40; + document.removeEventListener("rotarydetent", rotaryDetentHandler); + } + + /** + * Get value of step which is changed in each rotate + * @method getScrollStep + * @memberof ns.util.rotaryScrolling + * @return {number} + */ + function getScrollStep() { + return scrollStep; + } + + /** + * Set value of step which is changed in each rotate + * @param {number} newScrollStep New value of scroll step + * @method setScrollStep + * @memberof ns.util.rotaryScrolling + */ + function setScrollStep(newScrollStep) { + scrollStep = newScrollStep; + } + + rotaryScrolling.enable = enable; + rotaryScrolling.disable = disable; + rotaryScrolling.setScrollStep = setScrollStep; + rotaryScrolling.getScrollStep = getScrollStep; + + ns.util.rotaryScrolling = rotaryScrolling; + + }(document, window, ns)); + +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/*global window, define, ns */ +/** + * #Date Utility + * Object supports work with date and time + * @author Krzysztof Antoszek + * @class ns.util.date + */ +(function (ns) { + "use strict"; + var timeRegex = /([\-0-9.]*)(ms|s)?/i, + date = { + /** + * Convert string time length to miliseconds + * Note: this was implemented only for animation package + * and the string input should be conforming to css