From 803f1809c3b5cff5a4cd362d54200722fb3f72a2 Mon Sep 17 00:00:00 2001 From: Bob Builder Date: Thu, 15 Dec 2016 14:26:18 +0000 Subject: [PATCH] release version 1.0.2-b245 --- dist/mw-uikit.js | 6382 +++++++++++ dist/mw-uikit.min.js | 4 + dist/mw-uikit.relution.js | 9642 +++++++++++++++++ dist/mw-uikit.relution.min.js | 5 + dist/styles/_mw_ui.scss | 8 + dist/styles/mw-form/_mw_form.scss | 3 + dist/styles/mw-form/styles/_mixins.scss | 12 + .../mw-form/styles/_mw_checkbox_wrapper.scss | 5 + .../mw-form/styles/_mw_input_wrapper.scss | 62 + dist/styles/mw-inputs/_mw_inputs.scss | 4 + dist/styles/mw-inputs/styles/_mixins.scss | 68 + .../styles/mw-inputs/styles/_mw_checkbox.scss | 3 + dist/styles/mw-inputs/styles/_mw_radio.scss | 10 + dist/styles/mw-inputs/styles/_mw_select.scss | 34 + dist/styles/mw-layout/_mw_layout.scss | 9 + .../mw-layout/styles/_mw_column_layout.scss | 11 + dist/styles/mw-layout/styles/_mw_header.scss | 116 + dist/styles/mw-layout/styles/_mw_row.scss | 5 + .../mw-layout/styles/_mw_row_layout.scss | 4 + dist/styles/mw-layout/styles/_mw_sub_nav.scss | 14 + .../mw-layout/styles/_mw_sub_nav_pill.scss | 116 + dist/styles/mw-list/_mw_list.scss | 5 + dist/styles/mw-list/styles/_mw_list.scss | 205 + dist/styles/mw-list/styles/_mw_list_head.scss | 358 + dist/styles/mw-menu/_mw_menu.scss | 1 + .../mw-menu/styles/_mw_sidebar_menu.scss | 114 + dist/styles/mw-modal/_mw_modal.scss | 7 + dist/styles/mw-modal/styles/_mw_modal.scss | 139 + dist/styles/mw-toast/_mw_toast.scss | 1 + dist/styles/mw-toast/styles/_mw_toasts.scss | 171 + .../mw-ui-components/_mw_ui_components.scss | 20 + .../mw-ui-components/styles/_mw_alert.scss | 8 + .../styles/_mw_arrow_link.scss | 5 + .../mw-ui-components/styles/_mw_badge.scss | 39 + .../styles/_mw_bread_crumb.scss | 19 + .../styles/_mw_bread_crumbs_holder.scss | 4 + .../styles/_mw_button_help.scss | 68 + .../styles/_mw_collapsible.scss | 39 + .../styles/_mw_indefinite_loading.scss | 16 + .../styles/_mw_option_group.scss | 123 + .../mw-ui-components/styles/_mw_panel.scss | 11 + .../mw-ui-components/styles/_mw_spinner.scss | 55 + .../mw-ui-components/styles/_mw_tab_bar.scss | 38 + .../mw-ui-components/styles/_mw_tab_pane.scss | 3 + .../mw-ui-components/styles/_mw_timeline.scss | 19 + .../styles/_mw_timeline_entry.scss | 105 + .../styles/_mw_timeline_fieldset.scss | 73 + .../mw-ui-components/styles/_mw_toggle.scss | 52 + .../styles/_mw_view_change_loader.scss | 36 + .../styles/_mw_wizard_navigation.scss | 30 + .../styles/_mw_wizard_progress.scss | 19 + dist/styles/relution/_mw.scss | 51 + dist/styles/relution/src/_animations.scss | 137 + dist/styles/relution/src/_mw-file-upload.scss | 87 + dist/styles/relution/src/_mw-form.scss | 104 + .../relution/src/_mw-full-screen-height.scss | 8 + dist/styles/relution/src/_mw-header.scss | 8 + .../relution/src/_mw-markdown-preview.scss | 15 + .../relution/src/_mw-sidebar-filters.scss | 190 + dist/styles/relution/src/_mw-sidebar.scss | 156 + .../relution/src/_mw-version-selector.scss | 17 + 61 files changed, 19073 insertions(+) create mode 100644 dist/mw-uikit.js create mode 100644 dist/mw-uikit.min.js create mode 100644 dist/mw-uikit.relution.js create mode 100644 dist/mw-uikit.relution.min.js create mode 100644 dist/styles/_mw_ui.scss create mode 100644 dist/styles/mw-form/_mw_form.scss create mode 100644 dist/styles/mw-form/styles/_mixins.scss create mode 100644 dist/styles/mw-form/styles/_mw_checkbox_wrapper.scss create mode 100644 dist/styles/mw-form/styles/_mw_input_wrapper.scss create mode 100644 dist/styles/mw-inputs/_mw_inputs.scss create mode 100644 dist/styles/mw-inputs/styles/_mixins.scss create mode 100644 dist/styles/mw-inputs/styles/_mw_checkbox.scss create mode 100644 dist/styles/mw-inputs/styles/_mw_radio.scss create mode 100644 dist/styles/mw-inputs/styles/_mw_select.scss create mode 100644 dist/styles/mw-layout/_mw_layout.scss create mode 100644 dist/styles/mw-layout/styles/_mw_column_layout.scss create mode 100644 dist/styles/mw-layout/styles/_mw_header.scss create mode 100644 dist/styles/mw-layout/styles/_mw_row.scss create mode 100644 dist/styles/mw-layout/styles/_mw_row_layout.scss create mode 100644 dist/styles/mw-layout/styles/_mw_sub_nav.scss create mode 100644 dist/styles/mw-layout/styles/_mw_sub_nav_pill.scss create mode 100644 dist/styles/mw-list/_mw_list.scss create mode 100644 dist/styles/mw-list/styles/_mw_list.scss create mode 100644 dist/styles/mw-list/styles/_mw_list_head.scss create mode 100644 dist/styles/mw-menu/_mw_menu.scss create mode 100644 dist/styles/mw-menu/styles/_mw_sidebar_menu.scss create mode 100644 dist/styles/mw-modal/_mw_modal.scss create mode 100644 dist/styles/mw-modal/styles/_mw_modal.scss create mode 100644 dist/styles/mw-toast/_mw_toast.scss create mode 100644 dist/styles/mw-toast/styles/_mw_toasts.scss create mode 100644 dist/styles/mw-ui-components/_mw_ui_components.scss create mode 100644 dist/styles/mw-ui-components/styles/_mw_alert.scss create mode 100644 dist/styles/mw-ui-components/styles/_mw_arrow_link.scss create mode 100644 dist/styles/mw-ui-components/styles/_mw_badge.scss create mode 100644 dist/styles/mw-ui-components/styles/_mw_bread_crumb.scss create mode 100644 dist/styles/mw-ui-components/styles/_mw_bread_crumbs_holder.scss create mode 100644 dist/styles/mw-ui-components/styles/_mw_button_help.scss create mode 100644 dist/styles/mw-ui-components/styles/_mw_collapsible.scss create mode 100644 dist/styles/mw-ui-components/styles/_mw_indefinite_loading.scss create mode 100644 dist/styles/mw-ui-components/styles/_mw_option_group.scss create mode 100644 dist/styles/mw-ui-components/styles/_mw_panel.scss create mode 100644 dist/styles/mw-ui-components/styles/_mw_spinner.scss create mode 100644 dist/styles/mw-ui-components/styles/_mw_tab_bar.scss create mode 100644 dist/styles/mw-ui-components/styles/_mw_tab_pane.scss create mode 100644 dist/styles/mw-ui-components/styles/_mw_timeline.scss create mode 100644 dist/styles/mw-ui-components/styles/_mw_timeline_entry.scss create mode 100644 dist/styles/mw-ui-components/styles/_mw_timeline_fieldset.scss create mode 100644 dist/styles/mw-ui-components/styles/_mw_toggle.scss create mode 100644 dist/styles/mw-ui-components/styles/_mw_view_change_loader.scss create mode 100644 dist/styles/mw-ui-components/styles/_mw_wizard_navigation.scss create mode 100644 dist/styles/mw-ui-components/styles/_mw_wizard_progress.scss create mode 100644 dist/styles/relution/_mw.scss create mode 100644 dist/styles/relution/src/_animations.scss create mode 100644 dist/styles/relution/src/_mw-file-upload.scss create mode 100644 dist/styles/relution/src/_mw-form.scss create mode 100644 dist/styles/relution/src/_mw-full-screen-height.scss create mode 100644 dist/styles/relution/src/_mw-header.scss create mode 100644 dist/styles/relution/src/_mw-markdown-preview.scss create mode 100644 dist/styles/relution/src/_mw-sidebar-filters.scss create mode 100644 dist/styles/relution/src/_mw-sidebar.scss create mode 100644 dist/styles/relution/src/_mw-version-selector.scss diff --git a/dist/mw-uikit.js b/dist/mw-uikit.js new file mode 100644 index 00000000..44fddba2 --- /dev/null +++ b/dist/mw-uikit.js @@ -0,0 +1,6382 @@ +(function (root, angular) { + 'use strict'; + + angular.module('mwUI', [ + 'mwUI.Backbone', + 'mwUI.ExceptionHandler', + 'mwUI.Form', + 'mwUI.Inputs', + 'mwUI.i18n', + 'mwUI.Layout', + 'mwUI.List', + 'mwUI.Menu', + 'mwUI.Modal', + 'mwUI.ResponseHandler', + 'mwUI.Toast', + 'mwUI.ResponseToastHandler', + 'mwUI.Utils', + 'mwUI.UiComponents' + ]) + + .config(['i18nProvider', 'mwIconProvider', function (i18nProvider, mwIconProvider) { + i18nProvider.addLocale('de_DE', 'Deutsch', 'de_DE.json'); + i18nProvider.addLocale('en_US', 'English (US)', 'en_US.json'); + + mwIconProvider.addIconSet({ + id: 'mwUI', + classPrefix: 'fa', + iconsUrl:'uikit/mw_ui_icons.json' + }, true); + + }]) + + .run(['i18n', function(i18n){ + i18n.setLocale('en_US'); + }]); + + //This is only for backwards compatibility and should not be used + window.mCAP = window.mCAP || {}; + + root.mwUI = {}; + + //Will be replaced with the actual version number duringh the build process; + //DO NOT TOUCH + root.mwUI.VERSION = '1.0.2-b245'; + +angular.module("mwUI").run(["$templateCache", function($templateCache) { 'use strict'; + + $templateCache.put('uikit/mw-exception-handler/modals/templates/mw_exception_modal.html', + "

{{'ExceptionHandler.mwExceptionModal.unknownError' | i18n}}

{{exception}}

{{'ExceptionHandler.mwExceptionModal.userMessage' | i18n}}

{{'ExceptionHandler.mwExceptionModal.thanks' | i18n}}
" + ); + + + $templateCache.put('uikit/mw-form/directives/templates/mw_checkbox_wrapper.html', + "
" + ); + + + $templateCache.put('uikit/mw-form/directives/templates/mw_error_messages.html', + "
" + ); + + + $templateCache.put('uikit/mw-form/directives/templates/mw_input_wrapper.html', + "
0]\" ng-transclude>
0]\">
" + ); + + + $templateCache.put('uikit/mw-inputs/directives/templates/mw_checkbox_group.html', + "
" + ); + + + $templateCache.put('uikit/mw-inputs/directives/templates/mw_radio_group.html', + "
" + ); + + + $templateCache.put('uikit/mw-inputs/directives/templates/mw_select_box.html', + "" + ); + + + $templateCache.put('uikit/mw-inputs/directives/templates/mw_toggle.html', + "
" + ); + + + $templateCache.put('uikit/mw-layout/directives/templates/mw_app.html', + "Title" + ); + + + $templateCache.put('uikit/mw-layout/directives/templates/mw_header.html', + "

{{title}}

" + ); + + + $templateCache.put('uikit/mw-layout/directives/templates/mw_sub_nav.html', + "
    " + ); + + + $templateCache.put('uikit/mw-layout/directives/templates/mw_sub_nav_pill.html', + "
  • " + ); + + + $templateCache.put('uikit/mw-list/directives/templates/mw_list_body_row_checkbox.html', + " " + ); + + + $templateCache.put('uikit/mw-list/directives/templates/mw_list_footer.html', + "

    {{ 'List.mwListFooter.noneFound' | i18n }}

    " + ); + + + $templateCache.put('uikit/mw-list/directives/templates/mw_list_head.html', + "
    0}\">
    {{'List.mwListHead.selectAll' | i18n }} 0\" class=\"clickable clear\" ng-click=\"selectable.unSelectAll()\"> {{'List.mwListHead.clearSelection' | i18n}}
    0\" class=\"clickable\" ng-click=\"toggleShowSelected()\">{{'List.mwListHead.itemSelected' | i18n:{name: getModelAttribute(selectable.getSelected().first())} }} 1\">{{'List.mwListHead.itemsSelected' | i18n:{name: collectionName, count: selectedAmount} }}
    {{'List.mwListHead.itemAmount' | i18n:{name: collectionName, count: getTotalAmount()} }}
    {{'List.mwListHead.notAvailable' | i18n}} {{getModelAttribute(item)}}
    " + ); + + + $templateCache.put('uikit/mw-list/directives/templates/mw_list_header.html', + " " + ); + + + $templateCache.put('uikit/mw-menu/directives/templates/mw_menu.html', + "
    " + ); + + + $templateCache.put('uikit/mw-menu/directives/templates/mw_menu_entry.html', + "
    {{menuEntryCtrl.entry.get('label')}}
    " + ); + + + $templateCache.put('uikit/mw-menu/directives/templates/mw_menu_sub_entry.html', + "
    {{menuSubEntryCtrl.entry.get('label')}}
    " + ); + + + $templateCache.put('uikit/mw-menu/directives/templates/mw_sidebar_menu.html', + "
    " + ); + + + $templateCache.put('uikit/mw-modal/directives/templates/mw_modal.html', + "

    {{ title }}

    " + ); + + + $templateCache.put('uikit/mw-modal/directives/templates/mw_modal_body.html', + "
    " + ); + + + $templateCache.put('uikit/mw-modal/directives/templates/mw_modal_confirm.html', + "
    " + ); + + + $templateCache.put('uikit/mw-modal/directives/templates/mw_modal_footer.html', + "
    " + ); + + + $templateCache.put('uikit/mw-toast/directives/templates/mw_toasts.html', + "
    " + ); + + + $templateCache.put('uikit/mw-ui-components/directives/templates/mw_alert.html', + "
    " + ); + + + $templateCache.put('uikit/mw-ui-components/directives/templates/mw_arrow_link.html', + "" + ); + + + $templateCache.put('uikit/mw-ui-components/directives/templates/mw_badge.html', + "" + ); + + + $templateCache.put('uikit/mw-ui-components/directives/templates/mw_bread_crumb.html', + "
    {{title}}
    " + ); + + + $templateCache.put('uikit/mw-ui-components/directives/templates/mw_bread_crumbs_holder.html', + "
    " + ); + + + $templateCache.put('uikit/mw-ui-components/directives/templates/mw_collapsible.html', + "
    {{title}}
    " + ); + + + $templateCache.put('uikit/mw-ui-components/directives/templates/mw_hide_on_request.html', + "
    " + ); + + + $templateCache.put('uikit/mw-ui-components/directives/templates/mw_icon.html', + " " + ); + + + $templateCache.put('uikit/mw-ui-components/directives/templates/mw_indefinite_loading.html', + "
    {{'UiComponents.mwIndefiniteLoading.loading' | i18n | uppercase}}
    " + ); + + + $templateCache.put('uikit/mw-ui-components/directives/templates/mw_option_group.html', + "
    " + ); + + + $templateCache.put('uikit/mw-ui-components/directives/templates/mw_panel.html', + "

    {{title}}

    " + ); + + + $templateCache.put('uikit/mw-ui-components/directives/templates/mw_spinner.html', + "
    " + ); + + + $templateCache.put('uikit/mw-ui-components/directives/templates/mw_star_rating.html', + "" + ); + + + $templateCache.put('uikit/mw-ui-components/directives/templates/mw_tab_bar.html', + "
    " + ); + + + $templateCache.put('uikit/mw-ui-components/directives/templates/mw_tab_pane.html', + "
    " + ); + + + $templateCache.put('uikit/mw-ui-components/directives/templates/mw_text_collapsible.html', + "
    {{ text() }} {{ showLessOrMore() | i18n }}
    " + ); + + + $templateCache.put('uikit/mw-ui-components/directives/templates/mw_timeline.html', + "

    " + ); + + + $templateCache.put('uikit/mw-ui-components/directives/templates/mw_timeline_entry.html', + "
  • " + ); + + + $templateCache.put('uikit/mw-ui-components/directives/templates/mw_timeline_fieldset.html', + "
    {{mwTitle}}
    {{ hiddenEntriesText() | i18n:{count:entries.length} }}
    " + ); + + + $templateCache.put('uikit/mw-ui-components/directives/templates/mw_view_change_loader.html', + "
    " + ); + + + $templateCache.put('uikit/mw-ui-components/directives/templates/mw_wizard.html', + "
    " + ); + + + $templateCache.put('uikit/mw-ui-components/directives/templates/mw_wizard_navigation.html', + "
    " + ); + + + $templateCache.put('uikit/mw-ui-components/directives/templates/mw_wizard_progress.html', + "
    " + ); + + + $templateCache.put('uikit/mw-ui-components/directives/templates/mw_wizard_step.html', + "
    " + ); + + + $templateCache.put('uikit/mw-utils/modals/templates/mw_leave_confirmation_modal.html', + "

    {{ text }}

    " + ); + + + $templateCache.put('uikit/templates/mwSidebarBb/mwSidebarInput.html', + "
    " + ); + + + $templateCache.put('uikit/mw-exception-handler/i18n/de_DE.json', + "{ \"ExceptionHandler\": { \"mwExceptionModal\": { \"title\": \"Es ist etwas schiefgelaufen\", \"unknownError\": \"Leider ist ein unvorhergesehener Fehler aufgetreten. Sie können uns einen Fehlerbericht senden, sodass wir diesen schnellst möglich beseitigen können. Vielen Dank.\", \"userMessage\": \"Sie können uns zusätzlich ihre letzten Schritte beschreiben, sodass wir den Fehler schneller nachstellen können.\", \"userMessagePlaceholder\": \"(Optional)\", \"report\": \"Fehler melden\", \"thanks\": \"Vielen Dank für Ihre Rückmeldung. Wir werden uns umgehend um diesen Fehler kümmern.\" } } }" + ); + + + $templateCache.put('uikit/mw-exception-handler/i18n/en_US.json', + "{ \"ExceptionHandler\": { \"mwExceptionModal\": { \"title\": \"Something went wrong\", \"unknownError\": \"Unfortunatly something went wrong. You can report this error so we can fix it. Thank you.\", \"userMessage\": \"You can leave some additional information to make it easier for us to reproduce the error\", \"userMessagePlaceholder\": \"(Optional)\", \"report\": \"Report error\", \"thanks\": \"Thanks for your feedback. We will have a look at this error as soon as possible.\" } } }" + ); + + + $templateCache.put('uikit/mw-form/i18n/de_DE.json', + "{ \"mwErrorMessages\": { \"required\": \"ist ein Pflichtfeld\", \"hasToBeValidEmail\": \"muss eine valide E-Mail Adresse sein\", \"hasToMatchPattern\": \"muss dem Muster entsprechen\", \"hasToBeValidUrl\": \"muss eine valide URL sein\", \"hasToBeValidPhoneNumber\": \"muss eine gültige Telefonnummer sein\", \"hasToBeMin\": \"muss mindestens {{min}} sein\", \"hasToBeMinLength\": \"muss mindestens {{ngMinlength}} Zeichen haben\", \"hasToBeSmaller\": \"darf maximal {{max}} sein\", \"hasToBeSmallerLength\": \"darf maximal {{ngMaxlength}} Zeichen haben\" } }" + ); + + + $templateCache.put('uikit/mw-form/i18n/en_US.json', + "{ \"mwErrorMessages\": { \"required\": \"is required\", \"hasToBeValidEmail\": \"has to be a valid e-mail\", \"hasToMatchPattern\": \"has to match the pattern\", \"hasToBeValidUrl\": \"has to be a valid URL\", \"hasToBeValidPhoneNumber\": \"has to be a valid phone number\", \"hasToBeMin\": \"has to be at least {{min}}\", \"hasToBeMinLength\": \"has to have a least {{ngMinlength}} chars\", \"hasToBeSmaller\": \"must not be greater than {{max}}\", \"hasToBeSmallerLength\": \"must not have more chars than {{ngMaxlength}}\" } }" + ); + + + $templateCache.put('uikit/mw-inputs/i18n/de_DE.json', + "{ \"mwSelectBox\": { \"pleaseSelect\": \"Option auswählen\" } }" + ); + + + $templateCache.put('uikit/mw-inputs/i18n/en_US.json', + "{ \"mwSelectBox\": { \"pleaseSelect\": \"Select an option\" } }" + ); + + + $templateCache.put('uikit/mw-list/i18n/de_DE.json', + "{ \"List\": { \"mwListHead\": { \"items\": \"Einträge\", \"selectAll\": \"Alle selektieren\", \"clearSelection\": \"Selektion aufheben\", \"itemSelected\": \"{{name}} ist selektiert\", \"itemsSelected\": \"{{count}} {{name}} sind selektiert\", \"itemAmount\": \"{{count}} {{name}}\", \"searchFor\": \"{{name}} suchen\", \"notAvailable\": \"N/V\", \"notAvailableTooltip\": \"Der Eintrag ist nicht verfügbar. Eventuell wurde dieser gelöscht.\" }, \"mwListFooter\": { \"noneFound\": \"Es wurden keine Einträge gefunden\" } } }" + ); + + + $templateCache.put('uikit/mw-list/i18n/en_US.json', + "{ \"List\": { \"mwListHead\": { \"items\": \"Items\", \"selectAll\": \"Select all\", \"clearSelection\": \"Clear selection\", \"itemSelected\": \"{{name}} is selected\", \"itemsSelected\": \"{{count}} {{name}} are selected\", \"itemAmount\": \"{{count}} {{name}}\", \"searchFor\": \"Search for {{name}}\", \"notAvailable\": \"N/V\", \"notAvailableTooltip\": \"The entry is not available anymore. Maybe is has been deleted.\" }, \"mwListFooter\": { \"noneFound\": \"No entries have been found.\" } } }" + ); + + + $templateCache.put('uikit/mw-modal/i18n/de_DE.json', + "{ \"Modal\": { \"mwModalConfirm\": { \"areYouSure\": \"Sind Sie sich sicher?\" } } }" + ); + + + $templateCache.put('uikit/mw-modal/i18n/en_US.json', + "{ \"Modal\": { \"mwModalConfirm\": { \"areYouSure\": \"Are you sure?\" } } }" + ); + + + $templateCache.put('uikit/mw-ui-components/i18n/de_DE.json', + "{ \"UiComponents\": { \"mwToggle\": { \"on\": \"An\", \"off\": \"Aus\" }, \"mwTimelineFieldset\": { \"entriesHiddenSingular\": \"1 Eintrag ist ausgeblendet\", \"entriesHiddenPlural\": \"{{count}} Einträge sind ausgeblendet\" }, \"mwTextCollapsible\": { \"showMore\": \"mehr anzeigen\", \"showLess\": \"weniger anzeigen\" }, \"mwButtonHelp\": { \"isDisabledBecause\": \"Dieser Button ist deaktiviert weil:\" }, \"mwIndefiniteLoading\": { \"loading\": \"Lade Daten...\" } } }" + ); + + + $templateCache.put('uikit/mw-ui-components/i18n/en_US.json', + "{ \"UiComponents\": { \"mwToggle\": { \"on\": \"On\", \"off\": \"Off\" }, \"mwTimelineFieldset\": { \"entriesHiddenSingular\": \"One entry is hidden\", \"entriesHiddenPlural\": \"{{count}} entries are hidden\" }, \"mwTextCollapsable\": { \"showMore\": \"show more\", \"showLess\": \"show less\" }, \"mwButtonHelp\": { \"isDisabledBecause\": \"This button is currently disabled because:\" }, \"mwIndefiniteLoading\": { \"loading\": \"Loading data...\" } } }" + ); + + + $templateCache.put('uikit/mw-utils/i18n/de_DE.json', + "{ \"Utils\": { \"ok\": \"Ok\", \"cancel\": \"Abbrechen\", \"mwLeaveConfirmationModal\": { \"title\": \"Möchten Sie wirklich die aktuelle Seite verlassen?\", \"continue\": \"Fortfahren\", \"stay\": \"Auf Seite bleiben\" } } }" + ); + + + $templateCache.put('uikit/mw-utils/i18n/en_US.json', + "{ \"Utils\": { \"ok\": \"Ok\", \"cancel\": \"Cancel\" }, \"mwLeaveConfirmationModal\": { \"title\": \"Do you really want to leave the current page?\", \"continue\": \"Continue\", \"stay\": \"Stay on this page\" } }" + ); + + + $templateCache.put('uikit/mw_ui_icons.json', + "{ \"angleLeft\": \"fa-angle-left\", \"angleRight\": \"fa-angle-right\", \"angleUp\": \"fa-angle-up\", \"angleDown\": \"fa-angle-down\", \"caretRight\": \"fa-caret-right\", \"sort\": \"fa-sort\", \"sortAsc\": \"fa-sort-asc\", \"sortDesc\": \"fa-sort-desc\", \"warning\": \"fa-warning\", \"cross\": \"fa-times\", \"chevronUpCircle\": \"fa-chevron-circle-up\", \"chevronDownCircle\": \"fa-chevron-circle-down\", \"question\": \"fa-question\", \"questionCircle\": \"fa-question-circle-o\" }" + ); +}]); + +/** + * Created by zarges on 17/02/16. + */ +angular.module('mwUI.Utils', ['mwUI.i18n','mwUI.Modal']); + +window.mwUI.Utils = {}; +window.mwUI.Utils.shims = {}; + +angular.module('mwUI.Utils') + + .directive('mwDraggable', ['$timeout', function ($timeout) { + return { + restrict: 'A', + scope: { + mwDragData: '=', + //We can not use camelcase because *-start is a reserved word from angular! + mwDragstart: '&', + mwDragend: '&', + mwDropEffect: '@' + }, + link: function (scope, el) { + + el.attr('draggable', true); + el.addClass('draggable', true); + + if (scope.mwDragstart) { + el.on('dragstart', function (event) { + event.originalEvent.dataTransfer.setData('text', JSON.stringify(scope.mwDragData)); + event.originalEvent.dataTransfer.effectAllowed = scope.mwDropEffect; + $timeout(function () { + scope.mwDragstart({event: event, dragData: scope.mwDragData}); + }); + }); + } + + + el.on('dragend', function (event) { + if (scope.mwDragend) { + $timeout(function () { + scope.mwDragend({event: event}); + }); + } + }); + } + }; + }]); +angular.module('mwUI.Utils') + + .directive('mwDroppable', ['$timeout', function ($timeout) { + return { + restrict: 'A', + scope: { + mwDropData: '=', + mwDragenter: '&', + mwDragleave: '&', + mwDragover: '&', + mwDrop: '&', + disableDrop: '=' + }, + link: function (scope, el) { + + el.addClass('droppable'); + + var getDragData = function (event) { + var text = event.originalEvent.dataTransfer.getData('text'); + if (text) { + return JSON.parse(text); + } + }; + + if (scope.mwDragenter) { + el.on('dragenter', function (event) { + if (scope.disableDrop !== true) { + el.addClass('drag-over'); + } + $timeout(function () { + scope.mwDragenter({event: event}); + }); + }); + } + + if (scope.mwDragleave) { + el.on('dragleave', function (event) { + el.removeClass('drag-over'); + $timeout(function () { + scope.mwDragleave({event: event}); + }); + }); + } + + if (scope.mwDrop) { + el.on('drop', function (event) { + el.removeClass('drag-over'); + if (event.stopPropagation) { + event.stopPropagation(); // stops the browser executing other event listeners which are maybe deined in parent elements. + } + var data = getDragData(event); + $timeout(function () { + scope.mwDrop({ + event: event, + dragData: data, + dropData: scope.mwDropData + }); + }); + return false; + }); + } + + // Necessary. Allows us to drop. + var handleDragOver = function (ev) { + if (scope.disableDrop !== true) { + if (ev.preventDefault) { + ev.preventDefault(); + } + return false; + } + }; + el.on('dragover', handleDragOver); + + if (scope.mwDragover) { + el.on('dragover', function (event) { + $timeout(function () { + scope.mwDragover({event: event}); + }); + }); + } + + scope.$on('$destroy', function () { + el.off(); + }); + } + }; + }]); +angular.module('mwUI.Utils') + + .directive('mwInfiniteScroll', ['$window', function ($window) { + return { + restrict: 'A', + link: function (scope, el, attrs) { + + var collection, + loading = false, + throttledScrollFn, + scrollContainerEl, + scrollContentEl, + loadedPages = 0; + + if (attrs.mwListCollection) { + collection = scope.$eval(attrs.mwListCollection).getCollection(); + } else if (attrs.collection) { + collection = scope.$eval(attrs.collection); + } else { + console.warn('No collection was found for the infinite scroll pleas pass it as scope attribute'); + } + + if (!collection || (collection && !collection.filterable)) { + return; + } + + var loadNextPage = function () { + if (!loading && collection.filterable.hasNextPage()) { + loading = true; + loadedPages++; + return collection.filterable.loadNextPage().then(function () { + loading = false; + }); + } + }; + + // The threshold is controlled by how many pages are loaded + // The first pagination request should be done quite early so the user does not recognize that something + // is loaded. + // As scrollbar is getting longer and longer the threshold has to be also increased. + // Threshold starts at 40% and is increased by 10% until the max threshold of 90% is reached + var getLoadThreshold = function(){ + var minThreshold = 4, + maxThreshold = 9; + + return Math.min(minThreshold+loadedPages,maxThreshold)/10; + }; + + var scrollFn = function () { + var contentHeight = scrollContentEl[0].clientHeight || scrollContentEl.height(), + totalHeight = contentHeight - scrollContainerEl.height(), + threshold = getLoadThreshold(); + + if ( scrollContainerEl.scrollTop() / totalHeight > threshold) { + loadNextPage(); + } + }; + + if(attrs.scrollContainerSelector || attrs.scrollContentSelector){ + // Custom element defined by selectors. The scrollContainer is the element that has the scrollbar + // The scrollContent is the element inside the scroll container that should be scrolled + // At least one of them has to be defined and if you define both they must not be the same + if(attrs.scrollContainerSelector && el.parents(attrs.scrollContainerSelector).length > 0){ + scrollContainerEl = el.parents(attrs.scrollContainerSelector).first(); + } else if(attrs.scrollContainerSelector && el.parents(attrs.scrollContainerSelector).length===0){ + throw new Error ('No parent of the infinite scroll element with the selector '+attrs.scrollContainerSelector+' could be found!'); + } else { + scrollContainerEl = el; + } + + if(attrs.scrollContentSelector && el.find(attrs.scrollContentSelector).length > 0){ + scrollContentEl = el.find(attrs.scrollContentSelector).first(); + } else if(attrs.scrollContentSelector && el.find(attrs.scrollContentSelector).length === 0){ + throw new Error ('No child of the infinite scroll element with the selector '+attrs.scrollContentSelector+' could be found!'); + } else { + scrollContentEl = el; + } + } else if (el.parents('.modal').length) { + //element in modal + scrollContainerEl = el.parents('*[mw-modal-body]'); + scrollContentEl = el.parents('.modal-body'); + } else { + //element in window + scrollContainerEl = angular.element($window); + scrollContentEl = angular.element(document); + } + + if(scrollContainerEl === scrollContentEl){ + throw new Error('The scrollContainerElement can not be the same as the actual scrollContentElement'); + } + + throttledScrollFn = _.throttle(scrollFn, 500); + + // Register scroll callback + scrollContainerEl.on('scroll', throttledScrollFn); + + // Deregister scroll callback if scope is destroyed + scope.$on('$destroy', function () { + scrollContainerEl.off('scroll', throttledScrollFn); + }); + } + }; + }]); +angular.module('mwUI.Utils') + + .directive('mwLeaveConfirmation', ['$window', '$rootScope', 'LeaveConfirmationModal', function ($window, $rootScope, LeaveConfirmationModal) { + return { + scope: { + alertBeforeLeave: '=mwLeaveConfirmation', + text: '@' + }, + link: function (scope) { + + var confirmationModal = new LeaveConfirmationModal(); + + // Prevent the original event so the routing will not be completed + // Save the url where it should be navigated to in a temp variable + var showConfirmModal = function (nextUrl) { + confirmationModal.setScopeAttributes({ + nextUrl: nextUrl, + text: scope.text, + leaveCallback: function () { + scope.changeLocationOff(); + }, + stayCallback: function () { + + } + }); + confirmationModal.show(); + }; + + //In case that just a hashchange event was triggered + scope.changeLocationOff = $rootScope.$on('$locationChangeStart', function (ev, nextUrl) { + if (scope.alertBeforeLeave) { + ev.preventDefault(); + showConfirmModal(nextUrl); + } + }); + + //In case that the user clicks the refresh/back button or makes a hard url change + $window.onbeforeunload = function () { + if (scope.alertBeforeLeave) { + return scope.text; + } + }; + + if (!angular.isDefined(scope.text)) { + throw new Error('Please specify a text in the text attribute'); + } + + scope.$on('$destroy', scope.changeLocationOff); + } + }; + }]); + +angular.module('mwUI.Utils') + + .directive('mwPreventDefault', function () { + return { + restrict: 'A', + link: function (scope, elm, attr) { + if (!attr.mwPreventDefault) { + throw new Error('Directive mwPreventDefault: This directive must have an event name as attribute e.g. mw-prevent-default="click"'); + } + elm.on(attr.mwPreventDefault, function (event) { + event.preventDefault(); + }); + } + }; + }); +angular.module('mwUI.Utils') + + .directive('mwStopPropagation', function () { + return { + restrict: 'A', + link: function (scope, elm, attr) { + if (!attr.mwStopPropagation) { + throw new Error('Directive mwStopPropagation: This directive must have an event name as attribute e.g. mw-stop-propagation="keyup"'); + } + elm.on(attr.mwStopPropagation, function (event) { + event.stopPropagation(); + }); + } + }; + }); + +angular.module('mwUI.Utils') + + .filter('reduceStringTo', function () { + return function (input, count) { + if(count && input && input.length > count) { + return input.substr(0, count) + '...'; + } + return input; + }; + }); + +angular.module('mwUI.Utils') + + .factory('LeaveConfirmationModal', ['Modal', function (Modal) { + return Modal.prepare({ + templateUrl: 'uikit/mw-utils/modals/templates/mw_leave_confirmation_modal.html', + controller: 'LeaveConfirmationModalController' + }); + }]) + + .controller('LeaveConfirmationModalController', ['$scope', function ($scope) { + $scope.stay = function () { + $scope.stayCallback(); + $scope.hideModal(); + }; + + // User really wants to navigate to that page which was saved before in a temp variable + $scope.continue = function () { + if ($scope.nextUrl) { + //hide the modal and navigate to the page + $scope.leaveCallback(); + $scope.hideModal().then(function () { + document.location.href=$scope.nextUrl; + }); + } else { + throw new Error('NextUrl has to be set!'); + } + }; + }]); + +angular.module('mwUI.Utils') + + .service('callbackHandler', ['$injector', function($injector){ + return { + execFn: function(cb, params, scope){ + if(params && angular.isArray(params)){ + return cb.apply(scope, params); + } else { + return cb.call(scope, params); + } + }, + getFn: function(cb){ + if(angular.isString(cb)){ + return $injector.get(cb); + } else if(angular.isFunction(cb)){ + return cb; + } else { + throw new Error('First argument has to be either a valid service or function'); + } + }, + exec: function(cb, params, scope){ + return this.execFn(this.getFn(cb), params, scope); + } + }; + }]); + +var deepExtendObject = function (target, source) { + for (var key in source) { + if (key in target && _.isObject(target[key]) && _.isObject(source[key])) { + deepExtendObject(target[key], source[key]); + } else if (_.isObject(target[key]) && !_.isObject(source[key])) { + throw new Error('Target[' + key + '] is an object but source[' + key + '] is from type '+typeof source[key]+'! You can not overwrite an object with type '+typeof source[key]); + } else { + target[key] = source[key]; + } + } + return target; +}; + +window.mwUI.Utils.shims.deepExtendObject = deepExtendObject; +'use strict'; +window.mwUI.Utils.shims.domObserver = function (el, callback, config) { + + var observer = new MutationObserver(function (mutations) { + callback.call(this, mutations); + }), + node = (el instanceof angular.element) ? el[0] : el; + + config = config || { + attributes: true, + childList: true, characterData: true + }; + + observer.observe(node, config); + + return observer; +}; +/** + * Created by zarges on 17/02/16. + */ +window.mwUI.Utils.shims.routeToRegExp = function(route){ + var optionalParam = /\((.*?)\)/g, + namedParam = /(\(\?)?:\w+/g, + splatParam = /\*\w?/g, + escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; + + if(!_.isString(route)){ + throw new Error('The route ' + JSON.stringify(route) + 'has to be a URL'); + } + + route = route.replace(escapeRegExp, '\\$&') + .replace(optionalParam, '(?:$1)?') + .replace(namedParam, function (match, optional) { + return optional ? match : '([^/?]+)'; + }) + .replace(splatParam, '([^?]*?)'); + return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$'); +}; + +angular.module('mwUI.Utils').config(['i18nProvider', function(i18nProvider){ + i18nProvider.addResource('uikit/mw-utils/i18n'); +}]); + +window.mwUI.Backbone = { + hostName: '', + basePath: '', + Selectable: {}, + Utils: {}, + use$http: true +}; + +angular.module('mwUI.Backbone', []); + +mwUI.Backbone.Utils.concatUrlParts = function () { + var urlParts = _.toArray(arguments), cleanedUrlParts = []; + + //remove empty strings + urlParts = _.compact(urlParts); + + _.each(urlParts, function (url, index) { + if (index === 0) { + //remove only trailing slash + url = url.replace(/\/$/g, ''); + } else { + //Removing leading and trailing slash + url = url.replace(/^\/|\/$/g, ''); + } + cleanedUrlParts.push(url); + }); + + return cleanedUrlParts.join('/'); +}; +mwUI.Backbone.Utils.getUrl = function(instance){ + var hostName, basePath, endpoint; + + if(instance instanceof mwUI.Backbone.Model || instance instanceof mwUI.Backbone.Collection){ + hostName = _.result(instance, 'hostName') || ''; + basePath = _.result(instance, 'basePath') || ''; + endpoint = _.result(instance, 'endpoint'); + } else { + throw new Error('An instance of a collection or a model has to be passed as argument to the function'); + } + + if (!endpoint || endpoint.length === 0) { + throw new Error('An endpoint has to be specified'); + } + + return window.mwUI.Backbone.Utils.concatUrlParts(hostName, basePath, endpoint); +}; +mwUI.Backbone.Utils.request = function (url, method, options, instance) { + options = options || {}; + var requestOptions = { + url: url, + type: method + }, hostName; + + if (instance) { + requestOptions.instance = instance; + } + + if (url && !url.match(/\/\//)) { + if (instance instanceof mwUI.Backbone.Model || instance instanceof mwUI.Backbone.Collection) { + hostName = _.result(instance, 'hostName'); + } else { + hostName = mwUI.Backbone.hostName || ''; + } + requestOptions.url = mwUI.Backbone.Utils.concatUrlParts(hostName, url); + } + + return Backbone.ajax(_.extend(requestOptions, options)); +}; + +mwUI.Backbone.NestedModel = Backbone.NestedModel = Backbone.Model.extend({ + + nested: function () { + return {}; + }, + + _prepare: function () { + var nestedAttributes = this.nested(), + instanceObject = {}; + for (var key in nestedAttributes) { + if (typeof nestedAttributes[key] === 'function') { + var instance = new nestedAttributes[key](); + + instance.parent = this; + instanceObject[key] = instance; + } else { + throw new Error('Nested attribute ' + key + ' is not a valid constructor. Do not set an instance as nested attribute.'); + } + } + + return instanceObject; + }, + + _setNestedModel: function (key, value) { + if (_.isObject(value)) { + this.get(key).set(value); + } else { + var id = this.get(key).idAttribute; + this.get(key).set(id, value); + } + }, + + _setNestedCollection: function (key, value) { + if (_.isObject(value) && !_.isArray(value)) { + this.get(key).add(value); + } else if (_.isArray(value)) { + value.forEach(function (val) { + this._setNestedCollection(key, val); + }.bind(this)); + } else { + var id = this.get(key).model.prototype.idAttribute, + obj = {}; + + obj[id] = value; + this.get(key).add(obj); + } + }, + + _setNestedAttributes: function (obj) { + + for (var key in obj) { + var nestedAttrs = this.nested(), + value = obj[key], + nestedValue = nestedAttrs[key]; + + if (nestedValue && !(value instanceof nestedValue) && this.get(key)) { + + if (this.get(key) instanceof Backbone.Model) { + this._setNestedModel(key, value); + } else if (this.get(key) instanceof Backbone.Collection) { + this._setNestedCollection(key, value); + } + + delete obj[key]; + } + } + + return obj; + }, + + _nestedModelToJson: function (model) { + var result; + + if (model instanceof Backbone.NestedModel) { + result = model._prepareDataForServer(); + } else { + result = model.toJSON(); + } + + return result; + }, + + _prepareDataForServer: function () { + var attrs = _.extend({}, this.attributes), + nestedAttrs = this.nested(); + + for (var key in nestedAttrs) { + var nestedAttr = this.get(key); + + if (nestedAttr instanceof Backbone.Model) { + attrs[key] = this._nestedModelToJson(nestedAttr); + } else if (nestedAttr instanceof Backbone.Collection) { + var result = []; + + nestedAttr.each(function (model) { + result.push(this._nestedModelToJson(model)); + }.bind(this)); + + attrs[key] = result; + } + } + + return this.compose(attrs); + }, + + constructor: function (attributes, options) { + options = options || {}; + if (options.parse) { + attributes = this.parse(attributes); + options.parse = false; + } + this.attributes = this._prepare(); + this.set(attributes); + attributes = this.attributes; + return Backbone.Model.prototype.constructor.call(this, attributes, options); + }, + + set: function (attributes, options) { + var obj = {}; + + if (_.isString(attributes)) { + obj[attributes] = options; + } else if (_.isObject(attributes)) { + obj = attributes; + } + + if(!_.isObject(options)){ + options = null; + } + + obj = this._setNestedAttributes(obj); + + return Backbone.Model.prototype.set.call(this, obj, options); + }, + + compose: function (attrs) { + return attrs; + }, + + toJSON: function (options) { + // When options are set toJSON is called from the sync method so it is called before the object is send to the server + // We use this to transform our data before we are sending it to the server + // It is the counterpart of parse for the server + if (options) { + return this._prepareDataForServer(); + } else { + return Backbone.Model.prototype.toJSON.apply(this, arguments); + } + }, + + clear: function (options) { + var attrs = {}; + + for (var key in this.attributes){ + if(this.get(key) instanceof Backbone.Model){ + this.get(key).clear(); + } else if(this.get(key) instanceof Backbone.Collection){ + this.get(key).reset(); + } else { + attrs[key] = void 0; + } + } + + return this.set(attrs, _.extend({}, options, {unset: true})); + } +}); + + +/*jshint unused:false */ +mwUI.Backbone.Selectable.Model = function (modelInstance, options) { + + var _model = modelInstance, + _selected = options.selected || false; + + this.isInCollection = false; + + this.hasDisabledFn = (typeof options.isDisabled === 'function') || false; + + this.isDisabled = function () { + if (this.hasDisabledFn) { + return options.isDisabled.apply(modelInstance, arguments); + } + return false; + }; + + this.isSelected = function () { + return _selected; + }; + + this.select = function (options) { + options = options || {}; + if ( (!this.isDisabled() || options.force) && !this.isSelected()) { + _selected = true; + if(!options.silent){ + this.trigger('change change:select',modelInstance,this); + } + } + }; + + this.unSelect = function (options) { + options = options || {}; + if(this.isSelected()){ + _selected = false; + if(!options.silent){ + this.trigger('change change:unselect',modelInstance,this); + } + } + }; + + this.toggleSelect = function () { + if (this.isSelected()) { + this.unSelect(); + } else { + this.select(); + } + }; + + var main = function(){ + if (!(_model instanceof Backbone.Model)) { + throw new Error('First parameter has to be the instance of a model'); + } + }; + + main.call(this); +}; + +_.extend(mwUI.Backbone.Selectable.Model.prototype, Backbone.Events); +mwUI.Backbone.SelectableModel = Backbone.SelectableModel = Backbone.Model.extend({ + selectable: true, + selectableOptions: function(){ + return { + selected: false, + isDisabled: null + }; + }, + selectableModelConstructor: function(options){ + if (this.selectable) { + this.selectable = new mwUI.Backbone.Selectable.Model(this, this.selectableOptions.call(this, options)); + } + this.on('destroy', function(){ + //Decrement counter of parent collection when model is destroyed + if (this.collection && this.collection.filterable && this.collection.filterable.getTotalAmount() > 0) { + this.collection.filterable.setTotalAmount(this.collection.filterable.getTotalAmount() - 1); + } + }); + }, + constructor: function (attributes, options) { + var superConstructor = Backbone.Model.prototype.constructor.call(this, attributes, options); + this.selectableModelConstructor(options); + return superConstructor; + } + +}); +mwUI.Backbone.Model = mwUI.Backbone.NestedModel.extend({ + selectable: true, + hostName: function(){ + return mwUI.Backbone.hostName; + }, + basePath: function(){ + return mwUI.Backbone.basePath; + }, + endpoint: null, + selectableOptions: mwUI.Backbone.SelectableModel.prototype.selectableOptions, + urlRoot: function () { + return mwUI.Backbone.Utils.getUrl(this); + }, + constructor: function () { + var superConstructor = mwUI.Backbone.NestedModel.prototype.constructor.apply(this, arguments); + mwUI.Backbone.SelectableModel.prototype.selectableModelConstructor.apply(this, arguments); + return superConstructor; + }, + getEndpoint: function () { + return this.urlRoot(); + }, + setEndpoint: function (endpoint) { + this.endpoint = endpoint; + }, + sync: function (method, model, options) { + options.instance = this; + return mwUI.Backbone.NestedModel.prototype.sync.call(this, method, model, options); + }, + request: function (url, method, options) { + return mwUI.Backbone.Utils.request(url, method, options, this); + } +}); + + +mwUI.Backbone.Filter = function () { + // If it is an invalid value return null otherwise the provided object + var returnNullOrObjectFor = function (value, object) { + return (_.isUndefined(value) || value === null || value === '' || value.length===0 || (_.isArray(value) && _.compact(value).length===0)) ? null : object; + }; + + var returnNullOrObjectForMultipleValues = function (values, object) { + var hasValue = false; + if(!_.isObject(values)){ + console.log(values); + throw new Error('The argument values has to be an object'); + } + for(var key in values){ + if(returnNullOrObjectFor(values[key], true)){ + hasValue = true; + } else { + delete object[key]; + } + } + return hasValue ? object : null; + }; + + return { + containsString: function (fieldName, value) { + return returnNullOrObjectFor(value, { + type: 'containsString', + fieldName: fieldName, + contains: value + }); + }, + + string: function (fieldName, value) { + return returnNullOrObjectFor(value, { + type: 'string', + fieldName: fieldName, + value: value + }); + }, + + and: function (filters) { + return this.logOp(filters, 'AND'); + }, + + nand: function (filters) { + return this.logOp(filters, 'NAND'); + }, + + or: function (filters) { + return this.logOp(filters, 'OR'); + }, + + logOp: function (filters, operator) { + filters = _.without(filters, null); // Removing null values from existing filters + + return filters.length === 0 ? null : { // Ignore logOps with empty filters + type: 'logOp', + operation: operator, + filters: filters + }; + }, + + boolean: function (fieldName, value) { + return returnNullOrObjectFor(value, { + type: 'boolean', + fieldName: fieldName, + value: value + }); + }, + + stringMap: function (fieldName, key, value) { + if(value === '%%'){ + value = ''; + } + return returnNullOrObjectFor(value, { + type: 'stringMap', + fieldName: fieldName, + value: value, + key: key + }); + }, + + stringEnum: function (fieldName, values) { + return returnNullOrObjectFor(values, { + type: 'stringEnum', + fieldName: fieldName, + values: _.flatten(values) + }); + }, + + long: function (fieldName, value) { + return returnNullOrObjectFor(value, { + type: 'long', + fieldName: fieldName, + value: value + }); + }, + + like: function (fieldName, value) { + return returnNullOrObjectFor(value, { + type: 'like', + fieldName: fieldName, + like: value + }); + }, + + notNull: function (fieldName) { + return returnNullOrObjectFor(true, { + type: 'null', + fieldName: fieldName + }); + }, + + dateRange: function(fieldName, min, max){ + return returnNullOrObjectForMultipleValues({min: min, max: max}, { + type: 'dateRange', + fieldName: fieldName, + min: min, + max: max + }); + }, + + longRange: function(fieldName, min, max){ + return returnNullOrObjectForMultipleValues({min: min, max: max}, { + type: 'longRange', + fieldName: fieldName, + min: min, + max: max + }); + } + }; + +}; + +/*jshint unused:false */ +mwUI.Backbone.Filterable = function (collectionInstance, options) { + + options = options || {}; + + var _collection = collectionInstance, + _limit = options.limit, + _offset = _limit ? options.offset : false, + _page = options.page || 1, + _perPage = options.perPage || 30, + _initialFilterValues = {}, + _initialCustomUrlParams = _.clone(options.customUrlParams), + _filterDefinition = options.filterDefinition, + _sortOrder = options.sortOrder, + _totalAmount, + _lastFilter; + + this.filterValues = options.filterValues || {}; + this.customUrlParams = options.customUrlParams || {}; + this.fields = options.fields; + this.filterIsSet = false; + + this.hasFilterChanged = function (filter) { + return JSON.stringify(filter) !== JSON.stringify(_lastFilter); + }; + + this.getRequestParams = function (options) { + options.params = options.params || {}; + this.filterValues = _.extend({}, this.getInitialFilterValues(), this.filterValues); + // Filter functionality + var filter = this.getFilters(); + if (filter) { + options.params.filter = filter; + } + + //reset pagination if filter values change + if (this.hasFilterChanged(filter)) { + _page = 1; + } + + // Pagination functionality + if (_perPage && _page && (_limit || _.isUndefined(_limit))) { + options.params.limit = _perPage; + + // Calculate offset + options.params.offset = _page > 1 ? _perPage * (_page - 1) : 0; + } + + // Sort order + if (_sortOrder && _sortOrder.length > 0) { + options.params.sortOrder = _sortOrder; + } + + // Fallback to limit and offset if they're set manually, overwrites pagination settings + if (_limit || _offset) { + options.params.limit = _limit; + options.params.offset = _offset; + } + + if (_limit === false) { + delete options.params.limit; + } + + if (this.fields && this.fields.length > 0) { + options.params.field = this.fields; + } + + // Custom URL parameters + if (this.customUrlParams) { + _.extend(options.params, _.result(this, 'customUrlParams')); + } + + //always set non paged parameter + options.params.getNonpagedCount = true; + + _lastFilter = filter; + + return options.params; + }; + + this.getInitialFilterValues = function () { + return _initialFilterValues; + }; + + this.setInitialFilterValues = function (filterValues) { + _.extend(_initialFilterValues, filterValues); + }; + + this.setLimit = function (limit) { + _limit = limit; + _offset = _offset || 0; + }; + + this.setTotalAmount = function (totalAmount) { + _totalAmount = totalAmount; + }; + + this.getTotalAmount = function () { + return _totalAmount; + }; + + this.loadPreviousPage = function () { + _page -= 1; + return _collection.fetch({remove: false}); + }; + + this.hasPreviousPage = function () { + return _page >= 1; + }; + + this.loadNextPage = function () { + _page += 1; + return _collection.fetch({remove: false}); + }; + + this.hasNextPage = function () { + return _totalAmount && _totalAmount > _collection.length; + }; + + this.getPage = function () { + return _page; + }; + + this.getTotalPages = function () { + return Math.floor(_totalAmount / _perPage); + }; + + this.setSortOrder = function (sortOrder) { + _page = 1; + _sortOrder = sortOrder; + }; + + this.getSortOrder = function () { + return _sortOrder; + }; + + this.setFilters = function (filterMap) { + + _.forEach(filterMap, function (value, key) { + if (_.has(this.filterValues, key)) { + this.filterValues[key] = value; + } else { + throw new Error('Filter named \'' + key + '\' not found, did you add it to filterValues of the model?'); + } + }, this); + + this.filterIsSet = true; + + }; + + this.getFilters = function () { + if (_.isFunction(_filterDefinition)) { + return _filterDefinition.apply(this); + } + }; + + this.resetFilters = function () { + this.filterValues = this.getInitialFilterValues(); + this.customUrlParams = _initialCustomUrlParams; + this.filterIsSet = false; + }; + + (function _main() { + if (!(_collection instanceof Backbone.Collection)) { + throw new Error('First parameter has to be the instance of a collection'); + } + + if (options.filterValues) { + //This makes a clone from the current fitlervalues otherwise the objects are referencing to the same + var filterValuesClone = JSON.parse(JSON.stringify(options.filterValues)); + this.setInitialFilterValues(filterValuesClone); + } + + }.bind(this)()); +}; +mwUI.Backbone.FilterableCollection = Backbone.FilterableCollection = Backbone.Collection.extend({ + selectable: true, + filterableOptions: function () { + return { + limit: undefined, + offset: false, + page: 1, + perPage: 30, + filterValues: {}, + customUrlParams: {}, + filterDefinition: function () {}, + fields: [], + sortOrder: '' + }; + }, + filterableCollectionConstructor: function (options) { + if (this.filterable) { + this.filterable = new mwUI.Backbone.Filterable(this, this.filterableOptions.call(this, options)); + } + }, + constructor: function (attributes, options) { + var superConstructor = Backbone.Collection.prototype.constructor.call(this, attributes, options); + this.filterableCollectionConstructor(options); + return superConstructor; + }, + fetch: function (options) { + options = options || {}; + + if (this.filterable) { + var filterableParams = this.filterable.getRequestParams(options); + + if(window.mwUI.Backbone.use$http){ + //$http is using options.params to generate GET query params + options.params = options.params || {}; + _.extend(options.params, filterableParams); + } else { + //jquery ajax does not have a params a params option attribute for query params + options.data = options.data || {}; + _.extend(options.data, filterableParams); + } + } + + return Backbone.Collection.prototype.fetch.call(this, options); + } +}); +mwUI.Backbone.Selectable.Collection = function (collectionInstance, options) { + var _collection = collectionInstance, + _options = options || {}, + _modelHasDisabledFn = true, + _isSingleSelection = _options.isSingleSelection || false, + _addPreSelectedToCollection = _options.addPreSelectedToCollection || false, + _unSelectOnRemove = _options.unSelectOnRemove, + _preSelected = options.preSelected, + _selected = new Backbone.Collection(); + + var _preselect = function () { + if (_preSelected instanceof Backbone.Model) { + _isSingleSelection = true; + this.preSelectModel(_preSelected); + } else if (_preSelected instanceof Backbone.Collection) { + _isSingleSelection = false; + this.preSelectCollection(_preSelected); + } else { + throw new Error('The option preSelected has to be either a Backbone Model or Collection'); + } + }; + + var _bindModelOnSelectListener = function(model){ + this.listenTo(model.selectable, 'change:select', function(){ + if(!_selected.get(model)){ + this.select(model); + } + }.bind(this)); + }; + + var _bindModelOnUnSelectListener = function(model){ + this.listenTo(model.selectable, 'change:unselect', function(){ + if(_selected.get(model)) { + this.unSelect(model); + } + }.bind(this)); + }; + + var _setModelSelectableOptions = function (model, options) { + if(model && model.selectable){ + var selectedModel = _selected.get(model); + + if (selectedModel) { + if (_collection.get(model)) { + model.selectable.isInCollection = true; + selectedModel.selectable.isInCollection = true; + } else { + model.selectable.isInCollection = false; + selectedModel.selectable.isInCollection = false; + } + model.selectable.select(options); + selectedModel.selectable.select(options); + } else { + model.selectable.unSelect(options); + } + + _bindModelOnSelectListener.call(this,model); + _bindModelOnUnSelectListener.call(this,model); + } + }; + + var _updatePreSelectedModel = function(preSelectedModel, model){ + if(_preSelected){ + if(this.isSingleSelection()){ + _preSelected = model; + } else { + _preSelected.remove(preSelectedModel, {silent: true}); + _preSelected.add(model, {silent: true}); + } + } + }; + + var _updateSelectedModel = function(model){ + var selectedModel = this.getSelected().get(model); + if(selectedModel){ + _selected.remove(selectedModel, {silent: true}); + _selected.add(model, {silent: true}); + _updatePreSelectedModel.call(this,selectedModel, model); + _setModelSelectableOptions.call(this,model,{silent: true}); + } + }; + + this.getSelected = function () { + return _selected; + }; + + this.getDisabled = function () { + var disabled = new Backbone.Collection(); + if(_modelHasDisabledFn){ + _collection.each(function (model) { + if (model.selectable && model.selectable.isDisabled()) { + disabled.add(model); + } + }); + } + + return disabled; + }; + + /** + * + * @param model + */ + this.select = function (model, options) { + options = options || {}; + if (model instanceof Backbone.Model) { + if (!(model instanceof _collection.model)) { + model = new _collection.model(model.toJSON()); + } + + if (!model.selectable || (model.selectable.isDisabled() && !options.force)) { + return; + } + + if (_isSingleSelection) { + this.unSelectAll(); + } + + model.on('change', function(model, opts){ + opts = opts || {}; + if(opts.unset || !model.id || model.id.length<1){ + this.unSelect(model); + } + }, this); + + _selected.add(model); + _setModelSelectableOptions.call(this, model, options); + this.trigger('change change:add', model, this); + } else { + throw new Error('The first argument has to be a Backbone Model'); + } + }; + + this.selectAll = function () { + _collection.each(function (model) { + this.select(model); + }, this); + }; + + this.unSelect = function (model, options) { + options = options || {}; + _selected.remove(model); + _setModelSelectableOptions.call(this, model, options); + this.trigger('change change:remove', model, this); + }; + + this.unSelectAll = function () { + var selection = this.getSelected().clone(); + selection.each(function (model) { + this.unSelect(model); + },this); + }; + + this.toggleSelectAll = function () { + if (this.allSelected()) { + this.unSelectAll(); + } else { + this.selectAll(); + } + }; + + this.allSelected = function () { + var disabledModelsAmount = this.getDisabled().length; + + return this.getSelected().length === _collection.length - disabledModelsAmount; + }; + + this.allDisabled = function () { + return this.getDisabled().length === _collection.length; + }; + + this.isSingleSelection = function () { + return _isSingleSelection; + }; + + this.reset = function () { + this.unSelectAll(); + _preselect.call(this); + }; + + this.preSelectModel = function (model) { + if (model.id) { + + if (!_collection.get(model) && _addPreSelectedToCollection) { + _collection.add(model); + } + + this.select(model, {force: true, silent: true}); + } + }; + + this.preSelectCollection = function (collection) { + collection.each(function (model) { + this.preSelectModel(model); + }, this); + + collection.on('add', function (model) { + this.preSelectModel(model); + }, this); + + collection.on('remove', function (model) { + this.unSelect(model); + }, this); + + }; + + + var main = function(){ + if(!(_collection instanceof Backbone.Collection)){ + throw new Error('The first parameter has to be from type Backbone.Collection'); + } + + _collection.on('add', function (model) { + _modelHasDisabledFn = model.selectable.hasDisabledFn; + _setModelSelectableOptions.call(this,model); + _updateSelectedModel.call(this,model); + }, this); + + _collection.on('remove', function (model) { + if (_unSelectOnRemove) { + this.unSelect(model); + } else { + _setModelSelectableOptions.call(this,model); + } + }, this); + + _collection.on('reset', function () { + if (_unSelectOnRemove) { + this.unSelectAll(); + } else { + this.getSelected().each(function(model){ + _setModelSelectableOptions.call(this,model); + }, this); + } + }, this); + + if (_preSelected) { + _preselect.call(this); + } + }; + + main.call(this); + +}; + +_.extend(mwUI.Backbone.Selectable.Collection.prototype, Backbone.Events); +mwUI.Backbone.SelectableCollection = Backbone.SelectableCollection = Backbone.Collection.extend({ + selectable: true, + selectableOptions: function(){ + return { + isSingleSelection: false, + addPreSelectedToCollection: false, + unSelectOnRemove: false, + preSelected: new Backbone.Collection() + }; + }, + selectableCollectionConstructor: function(options){ + if (this.selectable) { + this.selectable = new mwUI.Backbone.Selectable.Collection(this, this.selectableOptions.call(this,options)); + } + }, + constructor: function (attributes, options) { + var superConstructor = Backbone.Collection.prototype.constructor.call(this, attributes, options); + this.selectableCollectionConstructor(options); + return superConstructor; + } +}); +mwUI.Backbone.Collection = Backbone.Collection.extend({ + selectable: true, + filterable: true, + hostName: function(){ + return mwUI.Backbone.hostName; + }, + basePath: function(){ + return mwUI.Backbone.basePath; + }, + endpoint: null, + selectableOptions: mwUI.Backbone.SelectableCollection.prototype.selectableOptions, + filterableOptions: mwUI.Backbone.FilterableCollection.prototype.filterableOptions, + model: mwUI.Backbone.Model, + url: function () { + return window.mwUI.Backbone.Utils.getUrl(this); + }, + getEndpoint: function () { + return this.url(); + }, + setEndpoint: function (endpoint) { + this.endpoint = endpoint; + }, + replace: function (models) { + this.reset(models); + this.trigger('replace', this); + }, + constructor: function () { + var superConstructor = Backbone.Collection.prototype.constructor.apply(this, arguments); + mwUI.Backbone.SelectableCollection.prototype.selectableCollectionConstructor.apply(this, arguments); + mwUI.Backbone.FilterableCollection.prototype.filterableCollectionConstructor.apply(this, arguments); + return superConstructor; + }, + fetch: function () { + return mwUI.Backbone.FilterableCollection.prototype.fetch.apply(this, arguments); + }, + request: function (url, method, options) { + return window.mwUI.Backbone.Utils.request(url, method, options, this); + } +}); + +angular.module('mwUI.Backbone') + + .directive('mwCollection', function () { + return { + require: '?ngModel', + link: function (scope, el, attrs, ngModel) { + var collection; + + var updateNgModel = function () { + if(collection.length>0){ + ngModel.$setViewValue(collection); + ngModel.$render(); + } else { + ngModel.$setViewValue(null); + ngModel.$render(); + } + }; + + + var init = function () { + collection = scope.$eval(attrs.mwCollection); + + if (collection) { + updateNgModel(); + collection.on('add remove reset', updateNgModel); + } + }; + + if (ngModel) { + if (scope.mwModel) { + init(); + } else { + var off = scope.$watch('mwCollection', function () { + off(); + init(); + }); + } + } + } + }; + }); +angular.module('mwUI.Backbone') + + .directive('mwModel', function () { + return { + require: '?ngModel', + link: function (scope, el, attrs, ngModelCtrl) { + var model, modelAttr; + + var updateNgModel = function () { + var val = model.get(modelAttr); + + ngModelCtrl.$formatters.forEach(function (formatFn) { + val = formatFn(val); + }); + + ngModelCtrl.$setViewValue(val); + ngModelCtrl.$render(); + }; + + var updateBackboneModel = function () { + var obj = {}; + + obj[modelAttr] = ngModelCtrl.$modelValue; + model.set(obj, {fromNgModel: true}); + }; + + var getModelAttrName = function () { + var mwModelAttrOption = attrs.mwModelAttr, + mwModelAttrFromNgModel = attrs.ngModel; + + if (mwModelAttrOption && mwModelAttrOption.length > 0) { + return mwModelAttrOption; + } else if (angular.isUndefined(mwModelAttrOption) && mwModelAttrFromNgModel) { + return mwModelAttrFromNgModel.split('.').pop(); + } + }; + + var init = function () { + model = scope.$eval(attrs.mwModel); + modelAttr = getModelAttrName(); + + if (ngModelCtrl && model && modelAttr) { + + model.on('change:' + modelAttr, function (model, val, options) { + if (!options.fromNgModel) { + updateNgModel(); + } + }); + + ngModelCtrl.$viewChangeListeners.push(updateBackboneModel); + ngModelCtrl.$parsers.push(function (val) { + updateBackboneModel(); + return val; + }); + + if (model.get(modelAttr) && ngModelCtrl.$modelValue && model.get(modelAttr) !== ngModelCtrl.$modelValue) { + throw new Error('The ng-model and the backbone model can not have different values during initialization!'); + } else if (model.get(modelAttr)) { + updateNgModel(); + } else if (ngModelCtrl.$modelValue) { + updateBackboneModel(); + } + } + }; + + if (scope.mwModel && getModelAttrName()) { + init(); + } else { + var offModel = scope.$watch('mwModel', function () { + offModel(); + init(); + }); + + var offModelAttr = scope.$watch('mwModelAttr', function () { + offModelAttr(); + init(); + }); + } + } + }; + }); + +var _backboneAjax = Backbone.ajax, + _backboneSync = Backbone.sync, + _$http, + _$q; + +angular.module('mwUI.Backbone') + + .run(['$http', '$q', function ($http, $q) { + _$http = $http; + _$q = $q; + }]); + +Backbone.ajax = function (options) { + if (mwUI.Backbone.use$http && _$http) { + // Set HTTP Verb as 'method' + options.method = options.type; + // Use angulars $http implementation for requests + return _$http.apply(angular, arguments).then(function(resp){ + if (options.success && typeof options.success === 'function') { + options.success(resp); + } + return resp; + }, function(resp){ + if (options.error && typeof options.error === 'function') { + options.error(resp); + } + return _$q.reject(resp); + }); + } else { + return _backboneAjax.apply(this, arguments); + } +}; + +Backbone.sync = function (method, model, options) { + // we have to set the flag to wait true otherwise all cases were you want to delete mutliple entries will break + // https://github.com/jashkenas/backbone/issues/3534 + // This flag means that the server has to confirm the creation/deletion before the model will be added/removed to the + // collection + options = options || {}; + if (_.isUndefined(options.wait)) { + options.wait = true; + } + // Instead of the response object we are returning the backbone model in the promise + return _backboneSync.call(Backbone, method, model, options).then(function () { + return model; + }); +}; +angular.module('mwUI.ExceptionHandler', ['mwUI.Modal', 'mwUI.i18n', 'mwUI.UiComponents', 'mwUI.Utils']); + +angular.module('mwUI.ExceptionHandler') + + .factory('ExceptionModal', ['Modal', function (Modal) { + return Modal.prepare({ + templateUrl: 'uikit/mw-exception-handler/modals/templates/mw_exception_modal.html', + controller: 'ExceptionModalController' + }); + }]) + + .controller('ExceptionModalController', ['$scope', '$q', 'Wizard', function ($scope, $q, Wizard) { + $scope.exception = null; + + $scope.wizard = Wizard.createWizard('exception'); + + $scope.report = function(){ + $q.when($scope.successCallback()).then(function(){ + $scope.wizard.next(); + }); + }; + + $scope.cancel = function(){ + if($scope.errorCallback){ + $q.when($scope.errorCallback()).then(function(){ + $scope.hideModal(); + }); + } else { + $scope.hideModal(); + } + }; + + $scope.close = function(){ + $scope.hideModal(); + }; + }]); + +angular.module('mwUI.ExceptionHandler') + + .provider('$exceptionHandler', function () { + + var _handlers = []; + + return { + + registerHandler: function(callback){ + _handlers.push(callback); + }, + + $get: ['callbackHandler', '$log', function (callbackHandler, $log) { + return function (exception,cause) { + exception = exception || ''; + cause = cause || ''; + + try{ + _handlers.forEach(function(callback){ + callbackHandler.exec(callback, [exception.toString(), cause.toString()]); + }); + } catch (err){ + $log.error(err); + } + + $log.error(exception); + }; + }] + }; + + }); +angular.module('mwUI.ExceptionHandler') + + .provider('exceptionHandlerModal', function () { + + var showExceptionModal = true, + options = { + displayException: false, + userCanEnterMessage: false, + successCallback: null, + errorCallback: null + }; + + return { + + disableExceptionModal: function () { + showExceptionModal = false; + }, + + setModalOptions: function (opts) { + _.extend(options, opts); + }, + + $get: ['callbackHandler', 'ExceptionModal', function (callbackHandler, ExceptionModal) { + + var exceptionModal = new ExceptionModal(); + + var hideNgView = function () { + var ngView = angular.element.find('div[ng-view]'); + + if (ngView) { + angular.element(ngView).hide(); + } + }; + + return function (exception, cause) { + if (showExceptionModal) { + + if (options.successCallback) { + var succCb = options.successCallback; + options.successCallback = function () { + return callbackHandler.exec(succCb, [exception.toString(), cause.toString()]); + }; + } + + if (options.errorCallback) { + var errCb = options.errorCallback; + options.errorCallback = function () { + callbackHandler.exec(errCb, [exception.toString(), cause.toString()]); + }; + } + + exceptionModal.setScopeAttributes(_.extend({}, options, { + exception: exception.toString(), + cause: cause + })); + + hideNgView(); + exceptionModal.show(); + } + }; + }] + }; + + }); + +angular.module('mwUI.ExceptionHandler').config(['$exceptionHandlerProvider', 'i18nProvider', function($exceptionHandlerProvider, i18nProvider){ + $exceptionHandlerProvider.registerHandler('exceptionHandlerModal'); + i18nProvider.addResource('uikit/mw-exception-handler/i18n'); +}]); +angular.module('mwUI.Form', ['mwUI.i18n','mwUI.Modal','mwUI.Utils']); + +angular.module('mwUI.Form') + + .directive('mwErrorMessages', ['mwValidationMessages', function (mwValidationMessages) { + return { + require: '^ngModelErrors', + templateUrl: 'uikit/mw-form/directives/templates/mw_error_messages.html', + link: function(scope, el, attrs, ngModelErrorsCtrl){ + scope.errors = ngModelErrorsCtrl.getErrors; + + scope.getMessageForError = function(errorModel){ + return mwValidationMessages.getMessageFor(errorModel.get('error'),errorModel.get('attrs')); + }; + } + }; + }]); +var extendForm = function () { + return { + restrict: 'E', + link: function (scope, el) { + el.addClass('form-horizontal'); + el.attr('novalidate', 'true'); + } + }; +}; + +angular.module('mwUI.Form') + + .directive('form', extendForm); +angular.module('mwUI.Form') + + .directive('mwFormLeaveConfirmation', ['$window', '$document', '$location', 'i18n', 'Modal', '$compile', function ($window, $document, $location, i18n, Modal, $compile) { + return { + require: '^form', + link: function (scope, elm, attr, formCtrl) { + + var confirmation = $compile('' + + '
    ' + + '
    ')(scope), + isActive = true; + + scope.showConfirmation = function () { + return formCtrl.$dirty && isActive; + }; + + elm.append(confirmation); + + scope.$on('$destroy', function () { + isActive = false; + }); + } + }; + }]); +angular.module('mwUI.Form') + + .directive('mwCheckboxWrapper', function () { + return { + transclude: true, + scope: { + label: '@', + tooltip: '@' + }, + templateUrl: 'uikit/mw-form/directives/templates/mw_checkbox_wrapper.html' + }; + }); +angular.module('mwUI.Form') + + .directive('mwInputWrapper', function () { + return { + transclude: true, + scope: { + label: '@', + tooltip: '@', + hideErrors: '=' + }, + templateUrl: 'uikit/mw-form/directives/templates/mw_input_wrapper.html', + controller: function () { + var modelState = { + dirty: true, + valid: true, + touched: false + }, + inputState = { + required: false, + focused: false + }, + inputType = 'text'; + + var setObj = function (obj, val) { + if (angular.isObject(val)) { + _.extend(obj, val); + } else { + throw new Error('State has to be an object'); + } + }; + + // Will be called by ngModel modification in mw-form/directives/ng-model + this.setModelState = function (newState) { + setObj(modelState, newState); + }; + + this.getModelState = function () { + return modelState; + }; + + // Will be called by ngModel modification in mw-form/directives/ng-model + this.setInputState = function (newState) { + setObj(inputState, newState); + }; + + this.getInputState = function () { + return inputState; + }; + + // Will be called by mwInputDefaults in mw-inputs/directives + this.setType = function(type){ + inputType = type; + }; + + this.getType = function(){ + return inputType; + }; + }, + link: function (scope, el, attrs, ctrl) { + + scope.isValid = function () { + return ctrl.getModelState().valid; + }; + + scope.isDirty = function () { + return ctrl.getModelState().dirty; + }; + + scope.isTouched = function () { + return ctrl.getModelState().touched; + }; + + scope.isRequired = function () { + return ctrl.getInputState().required; + }; + + scope.isFocused = function () { + return ctrl.getInputState().focused; + }; + + scope.hasError = function () { + return !scope.hideErrors && !scope.isValid() && scope.isDirty(); + }; + + scope.hasRequiredError = function () { + return scope.isRequired() && !scope.isValid(); + }; + + scope.getType = ctrl.getType; + } + }; + }); +angular.module('mwUI.Form') + + .directive('ngModel', function () { + return { + require: ['ngModel', '?^ngModelErrors', '?^mwInputWrapper'], + link: function (scope, el, attrs, ctrls) { + var ngModelCtrl = ctrls[0], + ngModelErrorsCtrl = ctrls[1], + mwInputWrapperCtrl = ctrls[2], + inputId = _.uniqueId('input_el'); + + var setErrors = function (newErrorObj, oldErrorObj) { + var newErrors = _.keys(newErrorObj), + oldErrors = _.keys(oldErrorObj), + removeErrors = _.difference(oldErrors, newErrors); + + ngModelErrorsCtrl.addErrorsForInput(newErrors, inputId, _.clone(attrs)); + ngModelErrorsCtrl.removeErrorsForInput(removeErrors, inputId, _.clone(attrs)); + }; + + var setModelState = function () { + mwInputWrapperCtrl.setModelState({ + dirty: ngModelCtrl.$dirty, + valid: ngModelCtrl.$valid, + touched: ngModelCtrl.$touched + }); + }; + + var initErrorState = function () { + scope.$watch(function () { + return ngModelCtrl.$error; + }, function (newErrorObj, oldErrorObj) { + setErrors(newErrorObj, oldErrorObj); + }, true); + }; + + var initModelAndInputState = function () { + scope.$watch(function () { + return ngModelCtrl.$error; + }, setModelState, true); + + scope.$watch(function () { + return ngModelCtrl.$touched; + }, setModelState); + + attrs.$observe('required', function(){ + mwInputWrapperCtrl.setInputState({ + required: angular.isDefined(el.attr('required')) + }); + }); + + el.on('focus blur', function(ev){ + mwInputWrapperCtrl.setInputState({ + focused: ev.type === 'focus' + }); + }); + scope.$on('$destroy', el.off.bind(el)); + }; + + if (ngModelErrorsCtrl) { + initErrorState(); + } + + if(mwInputWrapperCtrl){ + initModelAndInputState(); + } + } + }; + + }); +angular.module('mwUI.Form') + + .directive('ngModelErrors', function () { + return { + scope: true, + controller: function () { + var ErrorModel = mwUI.Backbone.Model.extend({ + idAttribute: 'error', + nested: function () { + return { + inputIds: Backbone.Collection + }; + } + }), + Errors = Backbone.Collection.extend({ + model: ErrorModel + }), + allErrors = new Errors(); + + var addErrorForInput = function (error, inputId, attrs) { + var alreadyExistingError = allErrors.get(error); + + if (alreadyExistingError) { + var inputIds = alreadyExistingError.get('inputIds'); + + _.extend(alreadyExistingError.get('attrs'),attrs); + inputIds.add({id: inputId}); + } else { + allErrors.add({error: error, inputIds: [inputId], attrs: attrs}); + } + }; + + var removeErrorForInput = function (error, inputId) { + var existingError = allErrors.get(error); + + if(existingError){ + var inputIdsInError = existingError.get('inputIds'), + inputIdModel = inputIdsInError.get(inputId); + + if (inputIdModel) { + inputIdsInError.remove(inputIdModel); + + if (inputIdsInError.length === 0) { + allErrors.remove(existingError); + } + } + } + }; + + this.addErrorsForInput = function (errors, inputId, attrs) { + errors.forEach(function(error){ + addErrorForInput(error, inputId, attrs); + }); + }; + + this.removeErrorsForInput = function (errors, inputId) { + errors.forEach(function(error){ + removeErrorForInput(error, inputId); + }); + }; + + this.getErrors = function(){ + return allErrors; + }; + + } + }; + }); + +angular.module('mwUI.Form') + + .provider('mwValidationMessages', function () { + var _stringValidators = {}, + _functionValidators = {}, + _executedValidators = {}; + + var _setValidationMessage = function (key, validationMessage) { + if (typeof validationMessage === 'function') { + _functionValidators[key] = validationMessage; + } else if (typeof validationMessage === 'string') { + _stringValidators[key] = validationMessage; + } else if (validationMessage) { + throw new Error('The validation has to be either a string or a function. String can be also a reference to i18n'); + } + }; + + this.registerValidator = function (key, validationMessage) { + if (!_stringValidators[key] && !_functionValidators[key]) { + _setValidationMessage(key, validationMessage); + } else { + throw new Error('The key ' + key + ' has already been registered'); + } + }; + + this.$get = ['$rootScope', 'i18n', function ($rootScope, i18n) { + var getTranslatedValidator = function (key, options) { + var message = _stringValidators[key]; + + if (i18n.translationIsAvailable(message)) { + return i18n.get(message, options); + } else { + return message; + } + }; + + var getExecutedValidator = function (key, options) { + return _functionValidators[key](i18n, options); + }; + + return { + getRegisteredValidators: function () { + return _.extend(_stringValidators, _executedValidators); + }, + getMessageFor: function (key, options) { + if (_functionValidators[key]) { + return getExecutedValidator(key, options); + } else if (_stringValidators[key]) { + return getTranslatedValidator(key, options); + } + }, + updateMessage: function (key, message) { + if (_stringValidators[key] || _functionValidators[key]) { + _setValidationMessage(key, message); + } else { + throw new Error('The key ' + key + ' is not available. You have to register it first via the provider'); + } + } + }; + }]; + }); + +angular.module('mwUI.Form') + + .config(['i18nProvider', 'mwValidationMessagesProvider', function(i18nProvider, mwValidationMessagesProvider){ + i18nProvider.addResource('uikit/mw-form/i18n'); + + mwValidationMessagesProvider.registerValidator('required','mwErrorMessages.required'); + mwValidationMessagesProvider.registerValidator('email','mwErrorMessages.hasToBeValidEmail'); + mwValidationMessagesProvider.registerValidator('pattern','mwErrorMessages.hasToMatchPattern'); + mwValidationMessagesProvider.registerValidator('url','mwErrorMessages.hasToBeValidUrl'); + mwValidationMessagesProvider.registerValidator('phone','mwErrorMessages.hasToBeValidPhoneNumber'); + mwValidationMessagesProvider.registerValidator('min','mwErrorMessages.hasToBeMin'); + mwValidationMessagesProvider.registerValidator('minlength','mwErrorMessages.hasToBeMinLength'); + mwValidationMessagesProvider.registerValidator('max','mwErrorMessages.hasToBeSmaller'); + mwValidationMessagesProvider.registerValidator('maxlength','mwErrorMessages.hasToBeSmallerLength'); + }]); +angular.module('mwUI.i18n', [ + +]); + +angular.module('mwUI.i18n') + + .provider('i18n', function () { + + var _resources = [], + _locales = [], + _dictionary = {}, + _isLoadingresources = false, + _oldLocale = null, + _defaultLocale = null; + + var _getActiveLocale = function () { + // This variable was set from 'LanguageService' in method setDefaultLocale() + return _.findWhere(_locales, {active: true}); + }; + + var _setActiveLocale = function (locale) { + var oldLocale = _getActiveLocale(), + newLocale = _.findWhere(_locales, {id: locale}); + + if (newLocale) { + if (oldLocale) { + oldLocale.active = false; + } + + newLocale.active = true; + } else { + throw new Error('You can not set a locale that has not been registered. Please register the locale first by calling addLocale()'); + } + }; + + /** + * Returns a translation for a key when a translation is available otherwise false + * @param key {String} + * @returns {*} + * @private + */ + var _getTranslationForKey = function (key) { + var activeLocale = _oldLocale || _getActiveLocale(); + + if (activeLocale && _dictionary && _dictionary[activeLocale.id]) { + var translation = _dictionary[activeLocale.id]; + angular.forEach(key.split('.'), function (k) { + translation = translation ? translation[k] : null; + }); + return translation; + } else { + return false; + } + }; + + /** + * Checks all locales for an available translation until it finds one + * @param property {String} + * @returns {*} + * @private + */ + var _getContentOfOtherLocale = function (property) { + var result; + _.each(_locales, function (locale) { + if (!result && property[locale.id]) { + result = property[locale.id]; + } + }); + if (!result) { + result = _.values(property)[0]; + } + return result; + }; + + /** + * Return all placeholders that are available in the translation string + * @param property {String} + * @returns {String} + * @private + */ + var _getUsedPlaceholdersInTranslationStr = function (str) { + + var re = /{{\s*([a-zA-Z0-9$_]+)\s*}}/g, + usedPlaceHolders = [], + matches; + + while ((matches = re.exec(str)) !== null) { + if (matches.index === re.lastIndex) { + re.lastIndex++; + } + usedPlaceHolders.push(matches[1]); + } + + return usedPlaceHolders; + }; + + /** + * Replaces placeholders in transaltion string with a value defined in the placeholder param + * @param str + * @param placeholder + * @returns {String} + * @private + */ + var _replacePlaceholders = function (str, placeholders) { + if (placeholders) { + var usedPlaceHolders = _getUsedPlaceholdersInTranslationStr(str); + usedPlaceHolders.forEach(function (usedPlaceholder) { + var escapedPlaceholder = usedPlaceholder.replace(/[$_]/g, '\\$&'), + replaceRegex = new RegExp('{{\\s*' + escapedPlaceholder + '\\s*}}'); + + str = str.replace(replaceRegex, placeholders[usedPlaceholder]); + }); + } + return str; + }; + + /** + * Registers a locale for which translations are available + * @param locale + * @param name + * @param fileExtension + */ + this.addLocale = function (locale, name, fileExtension) { + if (!_.findWhere(_locales, {id: locale})) { + _locales.push({ + id: locale, + name: name, + active: locale === _defaultLocale, + fileExtension: fileExtension || locale + '.json' + }); + _dictionary[locale] = {}; + } + }; + + /** + * Registers a resource so it can be accessed later by calling the public method get + * @param resourcePath {String} + * @param fileNameForLocale {String} + */ + this.addResource = function (resourcePath) { + if (!_.findWhere(_resources, {path: resourcePath})) { + _resources.push({ + path: resourcePath + }); + } + }; + + this.setDefaultLocale = function (locale) { + _defaultLocale = locale; + if (_.findWhere(_locales, {id: locale})) { + _setActiveLocale(locale); + } + }; + + this.$get = ['$templateRequest', '$q', '$rootScope', function ($templateRequest, $q, $rootScope) { + return { + /** + * Fills the dictionary with the translations by using the angular templateCache + * We need the dictionary because the get method has to be synchronous for the angular filter + * @param resourcePath {String} + */ + _loadResource: function (resourcePath) { + var resource = _.findWhere(_resources, {path: resourcePath}), + activeLocale = this.getActiveLocale(), + fileName = ''; + + if (resource && activeLocale) { + fileName = activeLocale.fileExtension; + + return $templateRequest(resource.path + '/' + fileName).then(function (content) { + _.extend(_dictionary[activeLocale.id], JSON.parse(content)); + return content; + }); + } else { + return $q.reject('Resource not available or no locale has been set'); + } + }, + + /** + * Returns all registered locales + * @returns {Array} + */ + getLocales: function () { + return _locales; + }, + + /** + * Return the currently active locale + * @returns {Object} + */ + getActiveLocale: function () { + return _getActiveLocale(); + }, + + /** + * translates key into current locale, given placeholders in {{placeholderName}} are replaced + * @param key {String} + * @param placeholder {Object} + */ + get: function (key, placeholder) { + var translation = _getTranslationForKey(key); + if (translation) { + return _replacePlaceholders(translation, placeholder); + } else if (_isLoadingresources) { + return '...'; + } else { + return 'MISSING TRANSLATION ' + this.getActiveLocale().id + ': ' + key; + } + }, + + /** + * set the locale and loads all resources for that locale + * @param locale {String} + */ + setLocale: function (localeid) { + var loadTasks = []; + $rootScope.$broadcast('i18n:loadResourcesStart'); + _isLoadingresources = true; + _oldLocale = this.getActiveLocale(); + _setActiveLocale(localeid); + _.each(_resources, function (resource) { + loadTasks.push(this._loadResource(resource.path)); + }, this); + return $q.all(loadTasks).then(function () { + _isLoadingresources = false; + $rootScope.$broadcast('i18n:loadResourcesSuccess'); + _oldLocale = null; + $rootScope.$broadcast('i18n:localeChanged', localeid); + return localeid; + }); + }, + + /** + * checks if a translation for the key is available + * @param key {String} + * @returns {boolean} + */ + translationIsAvailable: function (key) { + return !!_getTranslationForKey(key); + }, + /** + * return value of an internationalized object e.g {en_US:'English text', de_DE:'German text'} + * When no translation is availabe for the current set locale it tries the default locale. + * When no translation is available for the defaultLocale it tries all other available locales until + * a translation is found + * @param property {object} + * @returns {String} + */ + localize: function (property) { + var activeLocale = this.getActiveLocale(); + var p = property[activeLocale.id]; + if (angular.isDefined(p) && p !== '') { + return p; + } else { + return property[_defaultLocale] || _getContentOfOtherLocale(property); + } + }, + + extendForLocale: function (locale, translations) { + if (!locale) { + throw new Error('Locale is a required argument!'); + } + if (!_.isObject(translations)) { + throw new Error('The translations argument is of type ' + typeof translations + ' but it has to be an object!'); + } + if (!_.findWhere(_locales, {id: locale})) { + throw new Error('The locale ' + locale + ' does not exist! Make sure you have registered it.'); + } + if (!_isLoadingresources) { + mwUI.Utils.shims.deepExtendObject(_dictionary[locale], translations); + } + $rootScope.$on('i18n:loadResourcesSuccess', function () { + mwUI.Utils.shims.deepExtendObject(_dictionary[locale], translations); + }); + }, + + extend: function (localesWithTranslations) { + if (!_.isObject(localesWithTranslations)) { + throw new Error('The localesWithTranslations argument is from type ' + typeof localesWithTranslations + ' but it has to be an object!'); + } + + for (var locale in localesWithTranslations) { + this.extendForLocale(locale, localesWithTranslations[locale]); + } + } + }; + }]; + + }); +angular.module('mwUI.i18n') + + .filter('i18n', ['i18n', function (i18n) { + + function i18nFilter(translationKey, placeholder) { + if (_.isString(translationKey)) { + return i18n.get(translationKey, placeholder); + } else if(_.isObject(translationKey)){ + return i18n.localize(translationKey); + } + } + + i18nFilter.$stateful = true; + + return i18nFilter; + }]); +angular.module('mwUI.Inputs', ['mwUI.i18n', 'mwUI.Backbone']); + +angular.module('mwUI.Inputs') + + .directive('input', function () { + return { + restrict: 'E', + link: function (scope, el, attrs) { + // render custom checkbox + // to preserve the functionality of the original checkbox we just wrap it with a custom element + // checkbox is set to opacity 0 and has to be positioned absolute inside the custom checkbox element which has to be positioned relative + // additionally a custom status indicator is appended as a sibling of the original checkbox inside the custom checkbox wrapper + var render = function () { + var customCheckbox = angular.element(''), + customCheckboxStateIndicator = angular.element(''), + customCheckboxStateFocusIndicator = angular.element(''); + + el.wrap(customCheckbox); + customCheckboxStateIndicator.insertAfter(el); + customCheckboxStateFocusIndicator.insertAfter(customCheckboxStateIndicator); + }; + + var init = function() { + //after this the remaining element is removed + scope.$on('$destroy', function () { + el.off(); + el.parent('.mw-checkbox').remove(); + }); + + render(); + + }; + + if(attrs.type==='checkbox'){ + init(); + } + } + }; + }); +angular.module('mwUI.Inputs') + + .directive('mwCheckboxGroup', ['i18n', function (i18n) { + return { + restrict: 'A', + scope: { + mwCollection: '=', + mwOptionsCollection: '=', + mwOptionsLabelKey: '@', + mwOptionsLabelI18nPrefix: '@', + mwRequired: '=', + mwDisabled: '=' + }, + templateUrl: 'uikit/mw-inputs/directives/templates/mw_checkbox_group.html', + link: function (scope) { + scope.getLabel = function(model){ + var modelAttr = model.get(scope.mwOptionsLabelKey); + + if(modelAttr){ + if(scope.mwOptionsLabelI18nPrefix){ + return i18n.get(scope.mwOptionsLabelI18nPrefix+'.'+modelAttr); + } else { + return modelAttr; + } + } + }; + + scope.isOptionDisabled = function(model){ + return model.selectable.isDisabled(); + }; + + scope.toggleModel = function (model) { + var existingModel = scope.mwCollection.findWhere(model.toJSON()); + if (existingModel) { + scope.mwCollection.remove(existingModel); + } else { + scope.mwCollection.add(model.toJSON()); + } + }; + } + }; + }]); +var extendInput = function () { + return { + restrict: 'E', + require: '?^mwInputWrapper', + link: function (scope, el, attrs, mwInputWrapperCtrl) { + var skipTypes = ['radio','checkbox']; + + if(skipTypes.indexOf(attrs.type)===-1){ + el.addClass('form-control'); + } + + if(mwInputWrapperCtrl){ + if(attrs.type){ + mwInputWrapperCtrl.setType(attrs.type); + } else if(el[0].tagName){ + mwInputWrapperCtrl.setType(el[0].tagName.toLowerCase()); + } + } + } + }; +}; + +angular.module('mwUI.Inputs') + + .directive('select', extendInput) + + .directive('input', extendInput) + + .directive('textarea', extendInput); + +angular.module('mwUI.Inputs') + + .directive('input', function () { + return { + restrict: 'E', + link: function (scope, el, attrs) { + // render custom radio + // to preserve the functionality of the original checkbox we just wrap it with a custom element + // checkbox is set to opacity 0 and has to be positioned absolute inside the custom checkbox element which has to be positioned relative + // additionally a custom status indicator is appended as a sibling of the original checkbox inside the custom checkbox wrapper + var render = function () { + var customRadio = angular.element(''), + customRadioStateIndicator = angular.element(''), + customRadioStateFocusIndicator = angular.element(''); + + el.wrap(customRadio); + customRadioStateIndicator.insertAfter(el); + customRadioStateFocusIndicator.insertAfter(customRadioStateIndicator); + }; + + var init = function() { + //after this the remaining element is removed + scope.$on('$destroy', function () { + el.off(); + el.parent('.mw-radio').remove(); + }); + + render(); + + }; + + if(attrs.type === 'radio'){ + init(); + } + } + }; + }); +angular.module('mwUI.Inputs') + + .directive('mwRadioGroup', ['i18n', function (i18n) { + return { + restrict: 'A', + scope: { + mwModel: '=', + mwModelAttr: '@', + mwOptionsCollection: '=', + mwOptionsKey: '@', + mwOptionsLabelKey: '@', + mwOptionsLabelI18nPrefix: '@', + mwRequired: '=', + mwDisabled: '=' + }, + templateUrl: 'uikit/mw-inputs/directives/templates/mw_radio_group.html', + link: function (scope) { + scope.radioGroupId = _.uniqueId('radio_'); + + var setBackboneModel = function(model){ + if(scope.mwModelAttr){ + scope.mwModel.set(scope.mwModelAttr, model.get(scope.mwOptionsKey)); + } else { + scope.mwModel.set(model.toJSON()); + } + }; + + var unSetBackboneModel = function(){ + if(scope.mwModelAttr){ + scope.mwModel.unset(scope.mwModelAttr); + } else { + scope.mwModel.clear(); + } + }; + + scope.getLabel = function (model) { + var modelAttr = model.get(scope.mwOptionsLabelKey); + + if (modelAttr) { + if (scope.mwOptionsLabelI18nPrefix) { + return i18n.get(scope.mwOptionsLabelI18nPrefix + '.' + modelAttr); + } else { + return modelAttr; + } + } + }; + + scope.isOptionDisabled = function (model) { + return model.selectable.isDisabled(); + }; + + scope.getModelAttribute = function(){ + return scope.mwModelAttr || scope.mwModel.idAttribute; + }; + + scope.isChecked = function (model) { + if(scope.mwModelAttr){ + return model.get(scope.mwOptionsKey) === scope.mwModel.get(scope.mwModelAttr); + } else { + return model.id === scope.mwModel.id; + } + }; + + scope.selectOption = function (model) { + if (!scope.isChecked(model)) { + setBackboneModel(model); + } else { + unSetBackboneModel(); + } + }; + + if(scope.mwModelAttr && !scope.mwOptionsKey){ + throw new Error('[mwRadioGroup] When using mwModelAttr the attribute mwOptionsKey is required!'); + } + } + }; + }]); +angular.module('mwUI.Inputs') + + .directive('select', function () { + return { + link: function (scope, el) { + var customSelectWrapper = angular.element(''); + + var render = function () { + el.wrap(customSelectWrapper); + el.addClass('custom'); + }; + + render(); + } + }; + }); +angular.module('mwUI.Inputs') + + .directive('mwSelectBox', ['i18n', function (i18n) { + return { + restrict: 'A', + scope: { + mwModel: '=', + mwModelAttr: '@', + mwOptionsCollection: '=', + mwOptionsKey: '@', + mwOptionsLabelKey: '@', + mwOptionsLabelI18nPrefix: '@', + mwPlaceholder: '@', + mwRequired: '=', + mwDisabled: '=' + }, + templateUrl: 'uikit/mw-inputs/directives/templates/mw_select_box.html', + link: function (scope) { + + scope.viewModel = {}; + + var setBackboneModel = function(model){ + if(scope.mwModelAttr){ + scope.mwModel.set(scope.mwModelAttr, model.get(scope.mwOptionsKey)); + } else { + scope.mwModel.set(model.toJSON()); + } + }; + + var unSetBackboneModel = function(){ + if(scope.mwModelAttr){ + scope.mwModel.unset(scope.mwModelAttr); + } else { + scope.mwModel.clear(); + } + }; + + var setSelectedVal = function(){ + if(scope.mwModel.id){ + scope.viewModel.selected = scope.mwModel.id.toString(); + } + }; + + scope.getLabel = function (model) { + var modelAttr = model.get(scope.mwOptionsLabelKey); + + if (modelAttr) { + if (scope.mwOptionsLabelI18nPrefix) { + return i18n.get(scope.mwOptionsLabelI18nPrefix + '.' + modelAttr); + } else { + return modelAttr; + } + } + }; + + scope.hasPlaceholder = function(){ + return scope.mwPlaceholder || scope.mwRequired; + }; + + scope.getPlaceholder = function(){ + if(scope.mwPlaceholder){ + return scope.mwPlaceholder; + } else if(scope.mwRequired){ + return i18n.get('mwSelectBox.pleaseSelect'); + } + }; + + scope.isOptionDisabled = function (model) { + return model.selectable.isDisabled(); + }; + + scope.getModelAttribute = function(){ + return scope.mwModelAttr || scope.mwModel.idAttribute; + }; + + scope.isChecked = function (model) { + if(scope.mwModelAttr){ + return model.get(scope.mwOptionsKey) === scope.mwModel.get(scope.mwModelAttr); + } else { + return model.id === scope.mwModel.id; + } + }; + + scope.select = function(id){ + if(id){ + scope.selectOption(scope.mwOptionsCollection.get(id)); + } else { + unSetBackboneModel(); + } + }; + + scope.selectOption = function (model) { + if (!scope.isChecked(model)) { + setBackboneModel(model); + } else { + unSetBackboneModel(); + } + }; + + if(scope.mwModel){ + scope.mwModel.on('change:'+scope.mwModel.idAttribute, setSelectedVal); + setSelectedVal(); + } + + if(scope.mwModelAttr && !scope.mwOptionsKey){ + throw new Error('[mwRadioGroup] When using mwModelAttr the attribute mwOptionsKey is required!'); + } + } + }; + }]); +angular.module('mwUI.Inputs') + + .directive('mwToggle', ['$timeout', function ($timeout) { + return { + scope: { + mwModel: '=', + mwDisabled: '=', + mwChange: '&' + }, + replace: true, + templateUrl: 'uikit/mw-inputs/directives/templates/mw_toggle.html', + link: function (scope) { + scope.toggle = function (value) { + if (scope.mwModel !== value) { + scope.mwModel = !scope.mwModel; + $timeout(function () { + scope.mwChange({value: scope.mwModel}); + }); + } + }; + } + }; + }]); + +angular.module('mwUI.Inputs') + .config(['i18nProvider', function(i18nProvider) { + i18nProvider.addResource('uikit/mw-inputs/i18n'); + }]); +angular.module('mwUI.Layout', []); + +angular.module('mwUI.Layout') + + .directive('mwColumnLayout', function () { + return { + link: function(scope, el){ + el.addClass('mw-column-layout'); + } + }; + }); +angular.module('mwUI.Layout') + + .directive('mwHeader', ['$rootScope', '$route', '$location', function ($rootScope, $route, $location) { + return { + transclude: true, + scope: { + title: '@', + url: '@', + mwTitleIcon: '@', + showBackButton: '=', + mwBreadCrumbs: '=' + }, + templateUrl: 'uikit/mw-layout/directives/templates/mw_header.html', + link: function (scope, el, attrs, ctrl, $transclude) { + $rootScope.siteTitleDetails = scope.title; + + $transclude(function (clone) { + if ((!clone || clone.length === 0) && !scope.showBackButton) { + el.find('.mw-header').addClass('no-buttons'); + } + }); + + scope.refresh = function () { + $route.reload(); + }; + + if (!scope.url && scope.mwBreadCrumbs && scope.mwBreadCrumbs.length > 0) { + scope.url = scope.mwBreadCrumbs[scope.mwBreadCrumbs.length - 1].url; + scope.url = scope.url.replace('#', ''); + } else if (!scope.url && scope.showBackButton) { + console.error('Url attribute in header is missing!!'); + } + + scope.back = function () { + $location.path(scope.url); + }; + + } + }; + }]); +angular.module('mwUI.Layout') + + .directive('mwRow', function () { + return { + require: '^mwRowLayout', + link: function(scope, el, attrs, mwRowLayoutCtrl){ + var parObserver, + elObserver, + parentEl = el.parent(); + + var calculateHeight = function(){ + var rows = mwRowLayoutCtrl.getRegistered(), + heightOfOtherEls = 0; + + rows.forEach(function(row){ + if(row.el !== el){ + heightOfOtherEls += row.el.height(); + } + }); + el.css('height','calc(100vh - '+heightOfOtherEls+'px)'); + }; + + el.addClass('mw-row'); + mwRowLayoutCtrl.register({scope: scope, el:el}); + + if(angular.isDefined(attrs.mwExtend)){ + parObserver = mwUI.Utils.shims.domObserver(parentEl, calculateHeight); + elObserver = mwUI.Utils.shims.domObserver(el, calculateHeight); + scope.$on('$destroy', function(){ + parObserver.disconnect(); + elObserver.disconnect(); + }); + } + + } + }; + }); +angular.module('mwUI.Layout') + + .directive('mwRowLayout', function () { + return { + require: '?^^mwRowLayout', + controller: function(){ + var rows = []; + this.register = function(row){ + rows.push(row); + }; + + this.getRegistered = function(){ + return rows; + }; + }, + link: function(scope, el){ + el.addClass('mw-row-layout'); + } + }; + }); +angular.module('mwUI.Layout') + + .directive('mwSubNav', function () { + return { + restrict: 'A', + scope: { + justified: '=' + }, + transclude: true, + replace: true, + templateUrl: 'uikit/mw-layout/directives/templates/mw_sub_nav.html' + }; + }); +angular.module('mwUI.Layout') + + .directive('mwSubNavPill', ['$location', function ($location) { + return { + restrict: 'A', + scope: { + url: '@mwSubNavPill', + mwDisabled: '=' + }, + transclude: true, + replace: true, + templateUrl: 'uikit/mw-layout/directives/templates/mw_sub_nav_pill.html', + link: function (scope, elm) { + var setActiveClassOnUrlMatch = function (url) { + if (scope.url && url === scope.url.slice(1)) { + elm.addClass('active'); + } else { + elm.removeClass('active'); + } + }; + + scope.$watch('url', function (newUrlAttr) { + if (newUrlAttr) { + setActiveClassOnUrlMatch($location.$$path); + } + }); + + scope.navigate = function(url){ + if(!scope.mwDisabled){ + url = url.replace(/#\//,''); + $location.path(url); + $location.replace(); + } + }; + + setActiveClassOnUrlMatch($location.$$path); + + } + }; + }]); +angular.module('mwUI.List', ['mwUI.i18n', 'mwUI.Backbone', 'mwUI.UiComponents']); + +angular.module('mwUI.List') + + //Todo rename to mwList + .directive('mwListableBb', function(){ + return { + //TODO rename collection to mwCollection + //Move sort and filter persistance into filterable and remove mwListCollection + scope: { + collection: '=', + mwListCollection: '=' + }, + compile: function (elm) { + elm.append(''); + + return function (scope, elm) { + elm.addClass('table table-striped mw-list'); + }; + }, + controller: ['$scope', function ($scope) { + var _columns = $scope.columns = [], + _collection = null, + _mwListCollectionFilter = null; + + this.actionColumns = []; + + this.registerColumn = function (scope) { + _columns.push(scope); + }; + + this.unRegisterColumn = function (scope) { + if (scope && scope.$id) { + var scopeInArray = _.findWhere(_columns, {$id: scope.$id}), + indexOfScope = _.indexOf(_columns, scopeInArray); + + if (indexOfScope > -1) { + _columns.splice(indexOfScope, 1); + } + } + }; + + this.getColumns = function () { + return _columns; + }; + + this.getCollection = function () { + return _collection; + }; + + this.isSingleSelection = function () { + if (_collection && _collection.selectable) { + return _collection.selectable.isSingleSelection(); + } + return false; + }; + + $scope.$on('$destroy', function () { + this.actionColumns = []; + }.bind(this)); + + if ($scope.mwListCollection) { + _collection = $scope.mwListCollection.getCollection(); + _mwListCollectionFilter = $scope.mwListCollection.getMwListCollectionFilter(); + } else if ($scope.collection) { + _collection = $scope.collection; + } + }] + }; + }); +angular.module('mwUI.List') + + //TODO rename to mwListBodyRow + .directive('mwListableBodyRowBb', ['$timeout', function ($timeout) { + return { + restrict: 'A', + require: '^mwListableBb', + compile: function (elm) { + + elm.prepend(''); + + return function (scope, elm, attr, mwListCtrl) { + var selectedClass = 'selected'; + + scope.collection = mwListCtrl.getCollection(); + + if (!scope.item) { + throw new Error('No item available in the list! Please make sure to use ng-repeat="item in collection"'); + } + + if (scope.item && scope.item.selectable && !scope.item.selectable.isDisabled()) { + elm.addClass('selectable clickable'); + } else if (mwListCtrl.actionColumns && mwListCtrl.actionColumns.length > 0) { + elm.addClass('clickable'); + } + + elm.on('click', function () { + if (scope.item && scope.item.selectable && !scope.item.selectable.isDisabled()) { + $timeout(function () { + scope.item.selectable.toggleSelect(); + }); + } + }); + + elm.on('dblclick', function () { + if (mwListCtrl.actionColumns && angular.isNumber(scope.$index) && mwListCtrl.actionColumns[scope.$index]) { + document.location.href = mwListCtrl.actionColumns[scope.$index]; + } + }); + + scope.$watch('item.selectable.isSelected()', function (value) { + if (value) { + elm.addClass(selectedClass); + } else { + elm.removeClass(selectedClass); + } + }); + }; + } + }; + }]); +angular.module('mwUI.List') + + .directive('mwListBodyRowCheckbox', function () { + return { + restrict: 'A', + require: '^mwListableBb', + scope: { + item: '=' + }, + templateUrl: 'uikit/mw-list/directives/templates/mw_list_body_row_checkbox.html', + link: function (scope, elm, attr, mwListCtrl) { + scope.isSingleSelection = mwListCtrl.isSingleSelection(); + scope.click = function (item, $event) { + $event.stopPropagation(); + if (item.selectable) { + item.selectable.toggleSelect(); + } + }; + + scope.$watch('item.selectable.isDisabled()', function (isDisabled) { + if (isDisabled) { + scope.item.selectable.unSelect(); + } + }); + } + }; + }); +angular.module('mwUI.List') + //TODO rename + .directive('mwListableFooterBb', function () { + return { + require: '^mwListableBb', + templateUrl: 'uikit/mw-list/directives/templates/mw_list_footer.html', + link: function (scope, elm, attr, mwListCtrl) { + scope.collection = mwListCtrl.getCollection(); + scope.columns = mwListCtrl.getColumns(); + + scope.collection.on('request', function(){ + scope.isSynchronising = true; + }); + + scope.collection.on('sync error', function(){ + scope.isSynchronising = false; + }); + + scope.showSpinner = function(){ + return scope.isSynchronising && scope.collection.filterable.hasNextPage(); + }; + } + }; + }); +angular.module('mwUI.List') + + // TODO: rename to something else + // TODO: extract functionalities into smaller directives + .directive('mwListableHead2', ['$window', '$document', 'i18n', function ($window, $document, i18n) { + return { + scope: { + collection: '=', + affix: '=', + affixOffset: '=', + collectionName: '@', + nameFn: '&', + nameAttribute: '@', + localizeName: '@', + nameI18nPrefix: '@', + nameI18nSuffix: '@', + searchAttribute: '@' + }, + transclude: true, + templateUrl: 'uikit/mw-list/directives/templates/mw_list_head.html', + link: function (scope, el, attrs, ctrl, $transclude) { + var scrollEl, + bodyEl = angular.element('body'), + modalEl = el.parents('.modal .modal-body'), + canShowSelected = false, + _affix = angular.isDefined(scope.affix) ? scope.affix : true, + windowEl = angular.element($window); + + scope.selectable = false; + scope.selectedAmount = 0; + scope.collectionName = scope.collectionName || i18n.get('List.mwListHead.items'); + scope.isModal = modalEl.length > 0; + scope.isLoadingModelsNotInCollection = false; + scope.hasFetchedModelsNotInCollection = false; + scope.isLoadingModelsNotInCollection = false; + scope.hasFetchedModelsNotInCollection = false; + + + var newOffset; + + var throttledScrollFn = _.throttle(function () { + if (!newOffset) { + var headerOffset, + headerHeight, + headerBottomOffset, + listHeaderOffset, + spacer; + + if (scope.isModal) { + headerOffset = angular.element('.modal-header').offset().top; + headerHeight = angular.element('.modal-header').innerHeight(); + spacer = -3; + } else { + headerOffset = angular.element('[mw-header]').offset().top; + headerHeight = angular.element('[mw-header]').innerHeight(); + spacer = 5; + } + + headerBottomOffset = headerOffset + headerHeight; + listHeaderOffset = el.offset().top; + + newOffset = listHeaderOffset - headerBottomOffset - spacer; + } + + var scrollTop = scrollEl.scrollTop(); + + if (scrollTop > newOffset && _affix) { + el.find('.mw-listable-header').css('top', scrollTop - newOffset); + el.addClass('affixed'); + } else if (!_affix) { + scrollEl.off('scroll', throttledScrollFn); + } else { + el.find('.mw-listable-header').css('top', 'initial'); + el.removeClass('affixed'); + } + + }, 10); + + var throttledRecalculate = _.throttle(function () { + el.find('.mw-listable-header').css('top', 'initial'); + newOffset = null; + }); + + var loadItemsNotInCollection = function () { + if (scope.hasFetchedModelsNotInCollection) { + return; + } + var selectedNotInCollection = []; + scope.selectable.getSelected().each(function (model) { + if (!model.selectable.isInCollection && !scope.getModelAttribute(model)) { + selectedNotInCollection.push(model); + } + }); + + if (selectedNotInCollection.length === 0) { + return; + } + + var Collection = scope.collection.constructor.extend({ + filterableOptions: function () { + return { + filterDefinition: function () { + var filter = new window.mCAP.Filter(), + filters = []; + + selectedNotInCollection.forEach(function (model) { + if (model.id) { + filters.push( + filter.string(model.idAttribute, model.id) + ); + } + }); + + return filter.or(filters); + } + }; + } + }); + var collection = new Collection(); + collection.url = scope.collection.url(); + + scope.isLoadingModelsNotInCollection = true; + + collection.fetch().then(function (collection) { + scope.hasFetchedModelsNotInCollection = true; + var selected = scope.selectable.getSelected(); + collection.each(function (model) { + selected.get(model.id).set(model.toJSON()); + }); + + var deletedUuids = _.difference(_.pluck(selectedNotInCollection, 'id'), collection.pluck('uuid')); + + deletedUuids.forEach(function (id) { + selected.get(id).selectable.isDeletedItem = true; + }); + + scope.isLoadingModelsNotInCollection = false; + }); + }; + + scope.showSelected = function () { + canShowSelected = true; + loadItemsNotInCollection(); + setTimeout(function () { + var height; + if (scope.isModal) { + height = modalEl.height() + (modalEl.offset().top - el.find('.selected-items').offset().top) + 25; + modalEl.css('overflow', 'hidden'); + } else { + height = angular.element($window).height() - el.find('.selected-items').offset().top + scrollEl.scrollTop() - 25; + bodyEl.css('overflow', 'hidden'); + } + + el.find('.selected-items').css('height', height); + el.find('.selected-items').css('bottom', height * -1); + }); + }; + + scope.hideSelected = function () { + if (scope.isModal) { + modalEl.css('overflow', 'auto'); + } else { + bodyEl.css('overflow', 'inherit'); + } + canShowSelected = false; + }; + + scope.canShowSelected = function () { + return scope.selectable && canShowSelected && scope.selectedAmount > 0; + }; + + scope.unSelect = function (model) { + model.selectable.unSelect(); + }; + + scope.toggleSelectAll = function () { + scope.selectable.toggleSelectAll(); + }; + + scope.getTotalAmount = function () { + if (scope.collection.filterable && scope.collection.filterable.getTotalAmount()) { + return scope.collection.filterable.getTotalAmount(); + } else { + return scope.collection.length; + } + }; + + scope.toggleShowSelected = function () { + if (canShowSelected) { + scope.hideSelected(); + } else { + scope.showSelected(); + } + }; + + scope.getModelAttribute = function (model) { + if (scope.nameAttribute) { + var modelAttr = model.get(scope.nameAttribute); + + if (scope.nameI18nPrefix || scope.nameI18nSuffix) { + var i18nPrefix = scope.nameI18nPrefix || '', + i18nSuffix = scope.nameI18nSuffix || ''; + + return i18n.get(i18nPrefix + '.' + modelAttr + '.' + i18nSuffix); + } else if (angular.isDefined(scope.localizeName)) { + return i18n.localize(modelAttr); + } else { + return modelAttr; + } + } else { + return scope.nameFn({item: model}); + } + }; + + var init = function () { + scope.selectable = scope.collection.selectable; + if (scope.isModal) { + //element in modal + scrollEl = modalEl; + } + else { + //element in window + scrollEl = windowEl; + } + + // Register scroll callback + scrollEl.on('scroll', throttledScrollFn); + + scrollEl.on('resize', throttledRecalculate); + + // Deregister scroll callback if scope is destroyed + scope.$on('$destroy', function () { + scrollEl.off('scroll', throttledScrollFn); + }); + + scope.$on('$destroy', function () { + scrollEl.off('resize', throttledRecalculate); + }); + + el.on('focus', 'input[type=text]', function () { + el.find('.search-bar').addClass('focused'); + }); + + el.on('blur', 'input[type=text]', function () { + el.find('.search-bar').removeClass('focused'); + }); + }; + + $transclude(function (clone) { + if (clone && clone.length > 0) { + el.addClass('has-extra-content'); + } + }); + + scope.$watch(function () { + if (scope.selectable) { + return scope.selectable.getSelected().length; + } else { + return 0; + } + }, function (val) { + scope.selectedAmount = val; + if (val < 1) { + scope.hideSelected(); + } + }); + + scope.$watch('collection', function (collection) { + if (collection) { + init(); + } + }); + } + }; + }]); +angular.module('mwUI.List') + + //TODO rename to mwListHeader + .directive('mwListableHeaderBb', function () { + return { + require: '^mwListableBb', + scope: { + property: '@sort' + }, + transclude: true, + replace: true, + templateUrl: 'uikit/mw-list/directives/templates/mw_list_header.html', + link: function (scope, elm, attr, mwListCtrl) { + var ascending = '+', + descending = '-', + collection = mwListCtrl.getCollection(); + + var getSortOrder = function () { + if (collection && collection.filterable) { + return collection.filterable.getSortOrder(); + } else { + return false; + } + }; + + var sort = function (property, order) { + var sortOrder = order + property; + + collection.filterable.setSortOrder(sortOrder); + return collection.fetch(); + }; + + scope.canBeSorted = function(){ + return angular.isString(scope.property) && scope.property.length > 0 && !!collection.filterable; + }; + + scope.toggleSortOrder = function () { + if (scope.canBeSorted()) { + var sortOrder = ascending; //default + if (getSortOrder() === ascending + scope.property) { + sortOrder = descending; + } + sort(scope.property, sortOrder); + } + }; + + scope.isSelected = function (prefix) { + var sortOrder = getSortOrder(); + + if (sortOrder && prefix) { + return sortOrder === prefix + scope.property; + } else if(sortOrder && !prefix){ + return (sortOrder === '+' + scope.property || sortOrder === '-' + scope.property); + } + }; + + mwListCtrl.registerColumn(scope); + + scope.$on('$destroy', function () { + mwListCtrl.unRegisterColumn(scope); + }); + } + }; + }); +angular.module('mwUI.List') + + //TODO rename to mwListHeaderRow + .directive('mwListableHeaderRowBb', function () { + return { + require: '^mwListableBb', + scope: true, + compile: function (elm) { + elm.prepend(''); + elm.append(''); + + return function (scope, elm, attr, mwListCtrl) { + //empty collection is [] so ng-if would not work as expected + //we also have to check if the collection has a selectable + scope.hasCollection = false; + var collection = mwListCtrl.getCollection(); + if (collection) { + scope.hasCollection = angular.isDefined(collection.length) && collection.selectable; + } + scope.actionColumns = mwListCtrl.actionColumns; + }; + } + }; + }); +angular.module('mwUI.List') + //TODO rename to mwListUrlActionButton + .directive('mwListableLinkShowBb', function () { + return { + restrict: 'A', + require: '^mwListableBb', + scope: { + link: '@mwListableLinkShowBb', + target: '@?' + }, + template: '', + link: function (scope, elm, attr, mwListableCtrl) { + mwListableCtrl.actionColumns.push(scope.link); + } + }; + }); + +angular.module('mwUI.List') + + .config(['i18nProvider', function(i18nProvider){ + i18nProvider.addResource('uikit/mw-list/i18n'); + }]); + +/** + * Created by zarges on 23/02/16. + */ +angular.module('mwUI.Menu', []); + +window.mwUI.Menu = {}; + +/** + * Created by zarges on 15/02/16. + */ +var routeToRegex = mwUI.Utils.shims.routeToRegExp; + +var MwMenuEntry = window.mwUI.Backbone.NestedModel.extend({ + idAttribute: 'id', + defaults: function(){ + return { + url: null, + label: null, + icon: null, + activeUrls: [], + order: null + }; + }, + nested: function(){ + return { + subEntries: window.mwUI.Menu.MwMenuSubEntries + }; + }, + _throwMissingIdError: function(entry){ + throw new Error('No id is specified for the entry', entry); + }, + _throwNoTypeCouldBeDeterminedError: function(entry){ + throw new Error('No type could be determinded for the given entry: ',entry); + }, + _throwNotValidEntryError: function(entry){ + throw new Error('Is not a valid entry', entry); + }, + _determineType: function(entry){ + if(!entry.type){ + if(!entry.url && (!entry.subEntries || entry.subEntries.length===0) && !(entry.label || entry.icon)){ + entry.type='DIVIDER'; + } else if(entry.url || entry.subEntries && entry.subEntries.length>0 && (entry.label || entry.icon)){ + entry.type='ENTRY'; + } else { + this._throwNoTypeCouldBeDeterminedError(); + } + } + + return entry; + }, + _missingUrl: function(entry){ + return entry.type === 'ENTRY' && !entry.url && (!entry.subEntries || entry.subEntries.length === 0); + }, + _missingLabel: function(entry){ + return entry.type === 'ENTRY' && !entry.label && !entry.icon; + }, + isValidEntry: function(entry){ + if(entry.type){ + return !this._missingUrl(entry) && !this._missingLabel(entry); + } else { + return false; + } + }, + ownUrlIsActiveForUrl: function(url){ + if(this.get('url')){ + return url.match(routeToRegex(this.get('url'))); + } else { + return false; + } + }, + activeUrlIsActiveForUrl: function(url){ + var isActive = false; + this.get('activeUrls').forEach(function(activeUrl){ + if(!isActive){ + isActive = url.match(routeToRegex(activeUrl)); + } + }); + return isActive; + }, + isActiveForUrl: function(url){ + return this.ownUrlIsActiveForUrl(url) || this.activeUrlIsActiveForUrl(url); + }, + getActiveSubEntryForUrl: function(url){ + return this.get('subEntries').getActiveEntryForUrl(url); + }, + hasActiveSubEntryOrIsActiveForUrl: function(url){ + return this.get('type') === 'ENTRY' && (!!this.getActiveSubEntryForUrl(url) || this.isActiveForUrl(url)); + }, + constructor: function(entry, options){ + entry = this._determineType(entry); + if(!entry.id){ + this._throwMissingIdError(); + } + if(!this.isValidEntry(entry)){ + this._throwNotValidEntryError(); + } + return window.mwUI.Backbone.NestedModel.prototype.constructor.call(this, entry, options); + } +}); + +window.mwUI.Menu.MwMenuEntry = MwMenuEntry; + +/** + * Created by zarges on 15/02/16. + */ +var MwMenuEntries = Backbone.Collection.extend({ + model: window.mwUI.Menu.MwMenuEntry, + comparator: 'order', + _isAlreadyRegistered: function(entry) { + return ( + this.get(entry.id) || + (entry.url && this.findWhere({url: entry.url})) + ); + }, + _throwIsAlreadyRegisteredError: function(entry){ + if(entry.url){ + throw new Error('The entry with the id ' + entry.id + ' and the url ' + entry.url + ' has already been registered'); + } else { + throw new Error('The entry with the id ' + entry.id + ' has already been registered'); + } + }, + add: function(entries){ + if(_.isArray(entries)){ + entries.forEach(function(entry){ + if(this._isAlreadyRegistered(entry)){ + this._throwIsAlreadyRegisteredError(entry); + } + }.bind(this)); + } else { + if(this._isAlreadyRegistered(entries)){ + this._throwIsAlreadyRegisteredError(entries); + } + } + return Backbone.Collection.prototype.add.apply(this, arguments); + }, + addEntry: function(id, url, label, options){ + options = options || {}; + var addObj = { + id: id, + url: url, + label: label, + icon: options.icon, + activeUrls: options.activeUrls || [], + order: options.order, + subEntries: options.subEntries || [], + type: 'ENTRY' + }; + + return this.add(addObj); + }, + addDivider: function(id, options){ + options = options || {}; + var addObj = { + id: id, + label: options.label, + order: options.order, + type: 'DIVIDER' + }; + + return this.add(addObj); + }, + + getActiveEntryForUrl: function(url){ + var activeEntryFound = false, + activeEntry = null; + + this.each(function(model){ + if(!activeEntryFound && model.hasActiveSubEntryOrIsActiveForUrl(url)){ + activeEntryFound = true; + activeEntry = model; + } + }); + + return activeEntry; + } +}); + +window.mwUI.Menu.MwMenuEntries = MwMenuEntries; +/** + * Created by zarges on 15/02/16. + */ +var MwMenuSubEntries = window.mwUI.Menu.MwMenuEntries.extend({}); + +window.mwUI.Menu.MwMenuSubEntries = MwMenuSubEntries; +/** + * Created by zarges on 15/02/16. + */ +var MwMenu = window.mwUI.Menu.MwMenuEntries.extend({}); + +window.mwUI.Menu.MwMenu = MwMenu; + +/** + * Created by zarges on 23/02/16. + */ +angular.module('mwUI.Menu', []) + + .provider('mwSidebarMenu', function () { + + var mwMenu = new mwUI.Menu.MwMenu(), + logoUrl; + + this.getMenu = function () { + return mwMenu; + }; + + this.setLogoUrl = function(url){ + logoUrl = url; + }; + + this.getLogoUrl = function(){ + return logoUrl; + }; + + this.$get = function () { + return this; + }; + + }); + +/** + * Created by zarges on 23/02/16. + */ +angular.module('mwUI.Menu') + + .directive('mwMenu', function () { + return { + scope: { + menu: '=mwMenu' + }, + templateUrl: 'uikit/mw-menu/directives/templates/mw_menu.html' + }; + }); +/** + * Created by zarges on 23/02/16. + */ +angular.module('mwUI.Menu') + + .directive('mwMenuEntry', function () { + return { + scope: {}, + bindToController: { + entry: '=mwMenuEntry' + }, + templateUrl: 'uikit/mw-menu/directives/templates/mw_menu_entry.html', + controllerAs: 'menuEntryCtrl', + controller: ['$rootScope', '$location', function($rootScope, $location){ + this.isActive = this.entry.isActiveForUrl($location.url()); + + $rootScope.$on('$locationChangeSuccess', function () { + this.isActive = this.entry.hasActiveSubEntryOrIsActiveForUrl($location.url()); + }.bind(this)); + }] + }; + }); +/** + * Created by zarges on 23/02/16. + */ +angular.module('mwUI.Menu') + + .directive('mwMenuSubEntry', function () { + return { + scope: {}, + bindToController: { + entry: '=mwMenuSubEntry' + }, + templateUrl: 'uikit/mw-menu/directives/templates/mw_menu_sub_entry.html', + controllerAs: 'menuSubEntryCtrl', + controller: ['$rootScope', '$location', function($rootScope, $location){ + this.isActive = this.entry.isActiveForUrl($location.url()); + + $rootScope.$on('$locationChangeSuccess', function () { + this.isActive = this.entry.hasActiveSubEntryOrIsActiveForUrl($location.url()); + }.bind(this)); + }] + }; + }); +/** + * Created by zarges on 23/02/16. + */ +angular.module('mwUI.Menu') + + .directive('mwSidebarMenu', ['mwSidebarMenu', function (mwSidebarMenu) { + return { + templateUrl: 'uikit/mw-menu/directives/templates/mw_sidebar_menu.html', + transclude: true, + controllerAs: 'ctrl', + controller: function(){ + var isOpened = false; + this.mwMenu = mwSidebarMenu.getMenu(); + this.mwMenuLogo = mwSidebarMenu.getLogoUrl(); + + this.isOpened = function(){ + return isOpened; + }; + + this.toggleState = function(){ + isOpened = !isOpened; + }; + } + }; + }]); +angular.module('mwUI.Modal', ['mwUI.i18n', 'mwUI.Toast']); + +angular.module('mwUI.Modal') + + .directive('mwModal', ['mwModalTmpl', function (mwModalTmpl) { + return { + restrict: 'A', + scope: { + title: '@' + }, + transclude: true, + templateUrl: 'uikit/mw-modal/directives/templates/mw_modal.html', + link: function (scope) { + scope.$emit('COMPILE:FINISHED'); + scope.mwModalTmpl = mwModalTmpl; + } + }; + }]); +angular.module('mwUI.Modal') + + .directive('mwModalBody', function () { + return { + transclude: true, + templateUrl: 'uikit/mw-modal/directives/templates/mw_modal_body.html' + }; + }); +angular.module('mwUI.Modal') + + .directive('mwModalConfirm', function () { + return { + restrict: 'A', + transclude: true, + scope: true, + templateUrl: 'uikit/mw-modal/directives/templates/mw_modal_confirm.html', + link: function (scope, elm, attr) { + angular.forEach(['ok', 'cancel'], function (action) { + scope[action] = function () { + scope.$eval(attr[action]); + }; + }); + } + }; + }); +angular.module('mwUI.Modal') + + .directive('mwModalFooter', function () { + return { + transclude: true, + templateUrl: 'uikit/mw-modal/directives/templates/mw_modal_footer.html' + }; + }); + +angular.module('mwUI.Modal') + + .service('Modal', ['$rootScope', '$templateCache', '$document', '$compile', '$controller', '$injector', '$q', '$templateRequest', '$timeout', 'Toast', function ($rootScope, $templateCache, $document, $compile, $controller, $injector, $q, $templateRequest, $timeout, Toast) { + + var _openedModals = []; + + var Modal = function (modalOptions, bootStrapModalOptions) { + + var _id = modalOptions.templateUrl, + _scope = modalOptions.scope || $rootScope, + _scopeAttributes = modalOptions.scopeAttributes || {}, + _resolve = modalOptions.resolve || {}, + _controllerAs = modalOptions.controllerAs || '$ctrl', + _controller = modalOptions.controller, + _class = modalOptions.class || '', + _holderEl = modalOptions.el ? modalOptions.el : 'body .module-page', + _bootStrapModalOptions = bootStrapModalOptions || {}, + _modalOpened = false, + _self = this, + _modal, + _usedScope = _scope.$new(), + _usedController, + _bootstrapModal, + _previousFocusedEl; + + var _setAttributes = function (target, attributes) { + if (_.isObject(attributes) && _.isObject(target)) { + for (var key in attributes) { + target[key] = attributes[key]; + } + } + }; + + var _prepareController = function(locals){ + _setAttributes(_usedScope, _scopeAttributes); + + if (_controller) { + locals.$scope = _usedScope; + locals.modalId = _id; + _setAttributes(_controller, _scopeAttributes); + _usedController = $controller(_controller, locals, false, _controllerAs); + } + }; + + var _getTemplate = function () { + if (!_id) { + throw new Error('Modal service: templateUrl options is required.'); + } + return $templateRequest(_id); + }; + + var _bindModalCloseEvent = function () { + _bootstrapModal.on('hidden.bs.modal', function () { + _self.destroy(); + }); + }; + + var _destroyOnRouteChange = function () { + var changeLocationOff = $rootScope.$on('$locationChangeStart', function (ev, newUrl) { + if (_bootstrapModal && _modalOpened) { + ev.preventDefault(); + _self.hide().then(function () { + document.location.href = newUrl; + changeLocationOff(); + }); + } else { + changeLocationOff(); + } + }); + }; + + var _resolveLocals = function () { + var locals = angular.extend({}, _resolve); + angular.forEach(locals, function (value, key) { + locals[key] = angular.isString(value) ? + $injector.get(value) : + $injector.invoke(value, null, null, key); + }); + locals.$template = _getTemplate(); + return $q.all(locals); + }; + + var _compileTemplate = function (locals) { + _prepareController(locals); + return $compile(locals.$template)(_usedScope); + }; + + var _buildModal = function () { + + var dfd = $q.defer(); + + _resolveLocals().then(function (locals) { + _modal = _compileTemplate(locals); + + _usedScope.hideModal = function () { + return _self.hide(); + }; + + _usedScope.$on('COMPILE:FINISHED', function () { + _modal.addClass('mw-modal'); + _modal.addClass(_class); + _bootstrapModal = _modal.find('.modal'); + _bootStrapModalOptions.show = false; + _bootstrapModal.modal(_bootStrapModalOptions); + + // We need to overwrite the the original backdrop method with our own one + // to make it possible to define the element where the backdrop should be placed + // This enables us a backdrop per modal because we are appending the backdrop to the modal + // When opening multiple modals the previous will be covered by the backdrop of the latest opened modal + /* jshint ignore:start */ + if (_bootstrapModal.data()) { + var bootstrapModal = _bootstrapModal.data()['bs.modal'], + $bootstrapBackdrop = bootstrapModal.backdrop; + + bootstrapModal.backdrop = function (callback) { + $bootstrapBackdrop.call(bootstrapModal, callback, $(_holderEl).find('.modal')); + }; + } + /* jshint ignore:end */ + + _bindModalCloseEvent(); + _destroyOnRouteChange(); + dfd.resolve(); + }); + + }.bind(this), function (err) { + dfd.reject(err); + }); + + return dfd.promise; + }; + + this.id = _id; + + this.getScope = function () { + return _usedScope; + }; + + /** + * + * @ngdoc function + * @name mwModal.Modal#show + * @methodOf mwModal.Modal + * @function + * @description Shows the modal + */ + this.show = function () { + var dfd = $q.defer(); + Toast.clear(); + _previousFocusedEl = angular.element(document.activeElement); + $rootScope.$broadcast('$modalOpenStart'); + $rootScope.$broadcast('$modalResolveDependenciesStart'); + _buildModal.call(this).then(function () { + $rootScope.$broadcast('$modalResolveDependenciesSuccess'); + angular.element(_holderEl).append(_modal); + _bootstrapModal.modal('show'); + _modalOpened = true; + _openedModals.push(this); + _bootstrapModal.on('shown.bs.modal', function () { + angular.element(this).find('input:text:visible:first').focus(); + $rootScope.$broadcast('$modalOpenSuccess'); + dfd.resolve(); + }); + if (_previousFocusedEl) { + _bootstrapModal.on('hidden.bs.modal', function () { + _previousFocusedEl.focus(); + }); + } + + }.bind(this), function (err) { + $rootScope.$broadcast('$modalOpenError', err); + dfd.reject(err); + }); + + return dfd.promise; + }; + + this.setScopeAttributes = function (obj) { + _setAttributes(_scopeAttributes, obj); + + $timeout(function () { + if (_usedScope) { + _setAttributes(_usedScope, obj); + } + + if (_usedController) { + _setAttributes(_usedController, obj); + } + }); + }; + + /** + * + * @ngdoc function + * @name mwModal.Modal#hide + * @methodOf mwModal.Modal + * @function + * @description Hides the modal + * @returns {Object} Promise which will be resolved when modal is successfully closed + */ + this.hide = function () { + var dfd = $q.defer(); + + $rootScope.$broadcast('$modalCloseStart'); + if (_bootstrapModal && _modalOpened) { + _bootstrapModal.one('hidden.bs.modal', function () { + _bootstrapModal.off(); + _self.destroy(); + _modalOpened = false; + $rootScope.$broadcast('$modalCloseSuccess'); + dfd.resolve(); + }); + _bootstrapModal.modal('hide'); + } else { + dfd.resolve(); + } + + return dfd.promise; + }; + + /** + * + * @ngdoc function + * @name mwModal.Modal#toggle + * @methodOf mwModal.Modal + * @function + * @description Toggles the modal + * @param {String} modalId Modal identifier + */ + this.toggle = function () { + _bootstrapModal.modal('toggle'); + }; + + /** + * + * @ngdoc function + * @name mwModal.Modal#destroy + * @methodOf mwModal.Modal + * @function + * @description Removes the modal from the dom + */ + this.destroy = function () { + _openedModals = _.without(_openedModals, this); + var toasts = Toast.getToasts(); + toasts.forEach(function (toast) { + if (+new Date() - toast.initDate > 500) { + Toast.removeToast(toast.id); + } + }); + + $timeout(function () { + if (_modal) { + _modal.remove(); + _modalOpened = false; + } + + if (_usedScope) { + _usedScope.$destroy(); + } + + _scopeAttributes = modalOptions.scopeAttributes || {}; + }.bind(this)); + }; + + (function main() { + + _getTemplate(); + + _scope.$on('$destroy', function () { + _self.hide(); + }); + + })(); + + }; + + /** + * + * @ngdoc function + * @name mwModal.Modal#create + * @methodOf mwModal.Modal + * @function + * @description Create and initialize the modal element in the DOM. Available options + * + * - **templateUrl**: URL to a template (_required_) + * - **scope**: scope that should be available in the controller + * - **controller**: controller instance for the modal + * + * @param {Object} modalOptions The options of the modal which are used to instantiate it + * @returns {Object} Modal + */ + this.create = function (modalOptions, bootstrapModalOptions) { + return new Modal(modalOptions, bootstrapModalOptions); + }; + + this.prepare = function (modalOptions, bootstrapModalOptions) { + return this.create.bind(this, modalOptions, bootstrapModalOptions); + }; + + this.getOpenedModals = function () { + return _openedModals; + }; + }]); + +angular.module('mwUI.Modal') + + .provider('mwModalTmpl', function () { + + var _logoPath; + + this.setLogoPath = function (path) { + _logoPath = path; + }; + + this.$get = function () { + return { + getLogoPath: function () { + return _logoPath; + } + }; + }; + }); + +/* jshint ignore:start */ +// This is the orginal bootstrap backdrop implementation with the only +// modification that the element can be defined as parameter where the backdrop should be placed +$.fn.modal.Constructor.prototype.backdrop = function (callback, holderEl) { + var animate = this.$element.hasClass('fade') ? 'fade' : ''; + + if (this.isShown && this.options.backdrop) { + var doAnimate = $.support.transition && animate; + + this.$backdrop = $('