diff --git a/app/assets/javascripts/app/controllers/_ui_element/timer.coffee b/app/assets/javascripts/app/controllers/_ui_element/timer.coffee
new file mode 100644
index 000000000..860ccb8ce
--- /dev/null
+++ b/app/assets/javascripts/app/controllers/_ui_element/timer.coffee
@@ -0,0 +1,138 @@
+# coffeelint: disable=camel_case_classes
+class App.UiElement.timer
+ @render: (attribute) ->
+ days =
+ Mon: 'Monday'
+ Tue: 'Tuesday'
+ Wed: 'Wednesday'
+ Thu: 'Thursday'
+ Fri: 'Friday'
+ Sat: 'Saturday'
+ Sun: 'Sunday'
+ hours =
+ 0: '12 am'
+ 1: '1 am'
+ 2: '2 am'
+ 3: '3 am'
+ 4: '4 am'
+ 5: '5 am'
+ 6: '6 am'
+ 7: '7 am'
+ 8: '8 am'
+ 9: '9 am'
+ 10: '10 am'
+ 11: '11 am'
+ 12: '12 am'
+ 13: '1 pm'
+ 14: '2 pm'
+ 15: '3 pm'
+ 16: '4 pm'
+ 17: '5 pm'
+ 18: '6 pm'
+ 19: '7 pm'
+ 20: '8 pm'
+ 21: '9 pm'
+ 22: '10 pm'
+ 23: '11 pm'
+ hours =
+ 0: '00'
+ 1: '01'
+ 2: '02'
+ 3: '03'
+ 4: '04'
+ 5: '05'
+ 6: '06'
+ 7: '07'
+ 8: '08'
+ 9: '09'
+ 10: '10'
+ 11: '11'
+ 12: '12'
+ 13: '13'
+ 14: '14'
+ 15: '15'
+ 16: '16'
+ 17: '17'
+ 18: '18'
+ 19: '19'
+ 20: '20'
+ 21: '21'
+ 22: '22'
+ 23: '23'
+ minutes =
+ 0: '00'
+ 10: '10'
+ 20: '20'
+ 30: '30'
+ 40: '40'
+ 50: '50'
+
+ if !attribute.value
+ attribute.value = {}
+ if _.isEmpty(attribute.value.days)
+ attribute.value.days =
+ Mon: true
+ if _.isEmpty(attribute.value.hours)
+ attribute.value.hours =
+ 0: true
+ if _.isEmpty(attribute.value.minutes)
+ attribute.value.minutes =
+ 0: true
+
+ timer = $( App.view('generic/timer')( attribute: attribute, days: days, hours: hours, minutes: minutes ) )
+
+ timer.find('.select-value').bind('click', (e) =>
+ @select(e)
+ )
+ @createOutputString(timer)
+
+ timer
+
+ @select: (e) =>
+ target = $(e.currentTarget)
+
+ if target.hasClass('is-selected')
+ # prevent zero selections
+ if target.siblings('.is-selected').size() > 0
+ target.removeClass('is-selected')
+ target.next().val('false')
+ else
+ target.addClass('is-selected')
+ target.next().val('true')
+
+ formGroup = $(e.currentTarget).closest('.form-group')
+ @createOutputString(formGroup)
+
+ @createOutputString: (formGroup) =>
+ days = $.map(formGroup.find('[data-type=day]').filter('.is-selected'), (el) -> return $(el).text() )
+ hours = $.map(formGroup.find('[data-type=hour]').filter('.is-selected'), (el) -> return $(el).text() )
+ minutes = $.map(formGroup.find('[data-type=minute]').filter('.is-selected'), (el) -> return $(el).text() )
+
+ hours = @injectMinutes(hours, minutes)
+
+ days = @joinItems days
+ hours = @joinItems hours
+
+ formGroup.find('.js-timerResult').text(App.i18n.translateInline('Run every %s at %s', days, hours))
+
+ @injectMinutes: (hours, minutes) ->
+ newHours = [] # hours.length x minutes.length long
+
+ for hour in hours
+ # split off am/pm
+ [hour, suffix] = hour.split(' ')
+
+ for minute in minutes
+ combined = "#{ hour }:#{ minute }"
+ combined += " #{suffix}" if suffix
+
+ newHours.push combined
+
+ newHours
+
+ @joinItems: (items) ->
+ switch items.length
+ when 1 then return items[0]
+ when 2 then return "#{ items[0] } #{App.i18n.translateInline('and')} #{ items[1] }"
+ else
+ return "#{ items.slice(0, -1).join(', ') } #{App.i18n.translateInline('and')} #{ items[items.length-1] }"
diff --git a/app/assets/javascripts/app/views/generic/timer.jst.eco b/app/assets/javascripts/app/views/generic/timer.jst.eco
new file mode 100644
index 000000000..fd531f5fa
--- /dev/null
+++ b/app/assets/javascripts/app/views/generic/timer.jst.eco
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ <% for day, dayLong of @days: %>
+
<%- @T(dayLong) %>
+
+ <% end %>
+
+
+
+ <% for hour, hourLong of @hours: %>
+
<%- hourLong %>
+
+ <% end %>
+
+
+
+ <% for minute, minuteLong of @minutes: %>
+
<%- minuteLong %>
+
+ <% end %>
+
+
\ No newline at end of file
diff --git a/app/views/tests/form_timer.html.erb b/app/views/tests/form_timer.html.erb
new file mode 100644
index 000000000..5e9a1eaad
--- /dev/null
+++ b/app/views/tests/form_timer.html.erb
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/assets/tests/form_timer.js b/public/assets/tests/form_timer.js
new file mode 100644
index 000000000..dc0a1e4c2
--- /dev/null
+++ b/public/assets/tests/form_timer.js
@@ -0,0 +1,214 @@
+
+test("form elements check", function() {
+
+ $('#forms').append('
form elements check
')
+ var el = $('#form1')
+ var defaults = {
+ }
+ new App.ControllerForm({
+ el: el,
+ model: {
+ configure_attributes: [
+ { name: 'input1', display: 'Input1', tag: 'input', type: 'text', limit: 100, null: true, default: defaults['input1'] },
+ { name: 'timer_params', display: 'Timer', tag: 'timer', null: false, default: defaults['timer_params'] },
+ ]
+ },
+ autofocus: true
+ });
+
+ equal('Run every Monday at 00:00', el.find('.js-timerResult').val())
+
+ var params = App.ControllerForm.params(el)
+ var test_params = {
+ input1: '',
+ timer_params: {
+ days: {
+ 'Mon': true,
+ 'Tue': false,
+ 'Wed': false,
+ 'Thu': false,
+ 'Fri': false,
+ 'Sat': false,
+ 'Sun': false,
+ },
+ hours: {
+ 0: true,
+ 1: false,
+ 2: false,
+ 3: false,
+ 4: false,
+ 5: false,
+ 6: false,
+ 7: false,
+ 8: false,
+ 9: false,
+ 10: false,
+ 11: false,
+ 12: false,
+ 13: false,
+ 14: false,
+ 15: false,
+ 16: false,
+ 17: false,
+ 18: false,
+ 19: false,
+ 20: false,
+ 21: false,
+ 22: false,
+ 23: false,
+ },
+ minutes: {
+ 0: true,
+ 10: false,
+ 20: false,
+ 30: false,
+ 40: false,
+ 50: false,
+ },
+ },
+ }
+ deepEqual(params, test_params, 'form param check')
+
+ $('#forms').append('
form elements check
')
+ var el = $('#form2')
+ var defaults = {
+ input1: '123abc',
+ timer_params: {
+ days: {
+ 'Mon': true,
+ 'Fri': true,
+ },
+ hours: {
+ 0: true,
+ 10: true,
+ 16: true,
+ },
+ minutes: {
+ 0: true,
+ 10: true,
+ 50: true,
+ },
+ },
+ }
+ new App.ControllerForm({
+ el: el,
+ model: {
+ configure_attributes: [
+ { name: 'input1', display: 'Input1', tag: 'input', type: 'text', limit: 100, null: true, default: defaults['input1'] },
+ { name: 'timer_params', display: 'Timer', tag: 'timer', null: false, default: defaults['timer_params'] },
+ ]
+ },
+ autofocus: true
+ });
+
+ equal('Run every Monday and Friday at 00:00, 00:10, 00:50, 10:00, 10:10, 10:50, 16:00, 16:10 and 16:50', el.find('.js-timerResult').val())
+
+ var params = App.ControllerForm.params(el)
+ var test_params = {
+ input1: '123abc',
+ timer_params: {
+ days: {
+ 'Mon': true,
+ 'Tue': false,
+ 'Wed': false,
+ 'Thu': false,
+ 'Fri': true,
+ 'Sat': false,
+ 'Sun': false,
+ },
+ hours: {
+ 0: true,
+ 1: false,
+ 2: false,
+ 3: false,
+ 4: false,
+ 5: false,
+ 6: false,
+ 7: false,
+ 8: false,
+ 9: false,
+ 10: true,
+ 11: false,
+ 12: false,
+ 13: false,
+ 14: false,
+ 15: false,
+ 16: true,
+ 17: false,
+ 18: false,
+ 19: false,
+ 20: false,
+ 21: false,
+ 22: false,
+ 23: false,
+ },
+ minutes: {
+ 0: true,
+ 10: true,
+ 20: false,
+ 30: false,
+ 40: false,
+ 50: true,
+ },
+ },
+ }
+ deepEqual(params, test_params, 'form param check')
+
+ $('#form2 .js-day [data-value="Sat"]').click()
+ $('#form2 .js-hour [data-value="16"]').click()
+ $('#form2 .js-minute [data-value="10"]').click()
+
+ equal('Run every Monday, Friday and Samstag at 00:00, 00:50, 10:00 and 10:50', el.find('.js-timerResult').val())
+
+ var params = App.ControllerForm.params(el)
+ var test_params = {
+ input1: '123abc',
+ timer_params: {
+ days: {
+ 'Mon': true,
+ 'Tue': false,
+ 'Wed': false,
+ 'Thu': false,
+ 'Fri': true,
+ 'Sat': true,
+ 'Sun': false,
+ },
+ hours: {
+ 0: true,
+ 1: false,
+ 2: false,
+ 3: false,
+ 4: false,
+ 5: false,
+ 6: false,
+ 7: false,
+ 8: false,
+ 9: false,
+ 10: true,
+ 11: false,
+ 12: false,
+ 13: false,
+ 14: false,
+ 15: false,
+ 16: false,
+ 17: false,
+ 18: false,
+ 19: false,
+ 20: false,
+ 21: false,
+ 22: false,
+ 23: false,
+ },
+ minutes: {
+ 0: true,
+ 10: false,
+ 20: false,
+ 30: false,
+ 40: false,
+ 50: true,
+ },
+ },
+ }
+ deepEqual(params, test_params, 'form param check')
+
+});
\ No newline at end of file
diff --git a/test/browser/aab_unit_test.rb b/test/browser/aab_unit_test.rb
index 1cc92f5cf..8d54d4756 100644
--- a/test/browser/aab_unit_test.rb
+++ b/test/browser/aab_unit_test.rb
@@ -59,6 +59,13 @@ class AAbUnitTest < TestCase
value: '0',
)
+ location( url: browser_url + '/tests_form_timer' )
+ sleep 4
+ match(
+ css: '.result .failed',
+ value: '0',
+ )
+
location( url: browser_url + '/tests_form_extended' )
sleep 4
match(