diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Controllers/application.js b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Controllers/application.js new file mode 100644 index 00000000..7645bd8e --- /dev/null +++ b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Controllers/application.js @@ -0,0 +1,9 @@ +define([ + 'ember', + 'application' +], function (ember, app) { + app.ApplicationController = ember.Controller.extend({ + subscriptionDetails: null, // allow all Routes to access the subscriptionDetails + resetInputTextarea: false, // let the result Route reset the textArea when the details get sent + }); +}); \ No newline at end of file diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Controllers/input.js b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Controllers/input.js new file mode 100644 index 00000000..051fe9ca --- /dev/null +++ b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Controllers/input.js @@ -0,0 +1,79 @@ +define([ + 'ember', + 'application' +], function (ember, app) { + + // Dependencies of the computed properties: + // + // +-------------+ + // |TextareaValue| + // +-----+-------+ + // | + // +-----v----------------+ + // | strippedTextareaValue| + // +-----+-------------+--+ + // | | + // +-----v---------+ +-v-----------------+ + // |textareaIsEmpty| |subscriptionDetails| + // +-----+---------+ +-+-----------------+ + // | | + // +-----v---+ | + // |showError<---------+ + // +---------+ + app.InputController = ember.Controller.extend({ + + // the content of the textarea + textareaValue: '', + + // textareaValue without whitespace + strippedTextareaValue: ember.computed('textareaValue', function () { + var textareaValue = this.get('textareaValue'); + + // remove empty lines + textareaValue = textareaValue.replace(/(?:(?:^|\n)[\t ]*)+(?:$|\n)/g, '\n'); + + // remove first and last newline (if they exist) + textareaValue = textareaValue.replace(/^\n|\n$/g, ''); + + return textareaValue; + }), + + // this property is true when the textarea is empty + textareaIsEmpty: ember.computed('strippedTextareaValue', function () { + return this.get('strippedTextareaValue') === ''; + }), + + // this property contains the parsed subscriptionDetails of the textarea + subscriptionDetails: ember.computed('strippedTextareaValue', function () { + var rows = this.get('strippedTextareaValue').split('\n'); + var subscriptionDetails = []; + + for (var i = 0; i < rows.length; i++) { + var result = rows[i].match(/^(\d+)\t(\d+)\t(\d+)\t([^\t]+)$/); + + if (result === null) { + return null; + } + + subscriptionDetails.push(ember.Object.create({ + eventId: result[1], + personId: result[2], + subscriptionDetailId: result[3], + newValue: result[4] + })); + } + + return subscriptionDetails; + }), + + // the textarea is not empty and cannot be parsed + showError: ember.computed('textareaIsEmpty', 'subscriptionDetails', function () { + return this.get('textareaIsEmpty') === false && this.get('subscriptionDetails') === null; + }), + + // it's not possible to go to the next step when no subscriptionDetail can be parsed + cannotContinue: ember.computed('subscriptionDetails', function () { + return this.get('subscriptionDetails') === null; + }) + }); +}); \ No newline at end of file diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Controllers/validation.js b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Controllers/validation.js new file mode 100644 index 00000000..6f0cff0b --- /dev/null +++ b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Controllers/validation.js @@ -0,0 +1,13 @@ +define([ + 'ember', + 'application' +], function (ember, app) { + app.ValidationController = ember.Controller.extend({ + + cannotGoBack: ember.computed.not('model.everythingIsChecked'), + + cannotContinue: ember.computed('model.everythingIsChecked', 'model.thereAreOnlyErrors', function () { + return !this.get('model.everythingIsChecked') || this.get('model.thereAreOnlyErrors'); + }), + }); +}); \ No newline at end of file diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Controllers/vssver2.scc b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Controllers/vssver2.scc new file mode 100644 index 00000000..0320c00b Binary files /dev/null and b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Controllers/vssver2.scc differ diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/App/HtmlTemplates/application.html b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/HtmlTemplates/application.html new file mode 100644 index 00000000..e1075c67 --- /dev/null +++ b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/HtmlTemplates/application.html @@ -0,0 +1,3 @@ +
+ {{outlet}} +
\ No newline at end of file diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/App/HtmlTemplates/input.html b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/HtmlTemplates/input.html new file mode 100644 index 00000000..8343b524 --- /dev/null +++ b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/HtmlTemplates/input.html @@ -0,0 +1,17 @@ +
+ {{outlet "navigation"}} +
+

{{translate "readSubscriptionDetailsInfo"}}

{{translate "readSubscriptionDetailsFields"}}

+ {{textarea value=textareaValue rows=27 spellcheck=false class=(if showError "textarea--error")}} +
+
+
+ {{#if showError}} + {{translate "incorrectFormat"}} + {{/if}} +
+ {{#link-to "validation" tagName="button" class="btn btn-primary float-right" disabledWhen=cannotContinue}} +
{{translate "validate"}}
+ {{/link-to}} +
+
\ No newline at end of file diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/App/HtmlTemplates/navigation.html b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/HtmlTemplates/navigation.html new file mode 100644 index 00000000..909d5893 --- /dev/null +++ b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/HtmlTemplates/navigation.html @@ -0,0 +1,9 @@ +
+

+ {{#link-to "input" disabled=true}}{{translate "input"}}{{/link-to}} + › + {{#link-to "validation" disabled=true}}{{translate "validation"}}{{/link-to}} + › + {{#link-to "result" disabled=true}}{{translate "result"}}{{/link-to}} +

+
\ No newline at end of file diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/App/HtmlTemplates/result.html b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/HtmlTemplates/result.html new file mode 100644 index 00000000..17d9459a --- /dev/null +++ b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/HtmlTemplates/result.html @@ -0,0 +1,60 @@ +
+ {{outlet "navigation"}} + + + + + + + + + + + + + + {{#each model.subscriptionDetails as |subscriptionDetail|}} + {{#unless subscriptionDetail.error}} + + + + + + + + + + {{/unless}} + {{/each}} + +
{{translate "event"}}{{translate "name"}}{{translate "subscriptionDetail"}}{{translate "oldValue"}}{{translate "newValue"}}{{translate "status"}}{{translate "fehler"}}
{{subscriptionDetail.event}}{{subscriptionDetail.name}}{{subscriptionDetail.subscriptionDetail}}{{subscriptionDetail.oldValue}}{{subscriptionDetail.newValue}} +
+ {{#if subscriptionDetail.loading}} +
+
+ {{else}} + {{#if subscriptionDetail.uploaded}} + check_circle + {{else}} + cancel
+ {{/if}} + {{/if}} +
+
{{subscriptionDetail.errorUpload}}
+
+
+ {{#if model.error}} + + {{translate "goBackToStartError"}} + + {{else}} +
+ {{translate "goBackToStart"}} +
+ {{/if}} +
+ {{#link-to "input" tagName="button" class="btn btn-primary float-right" disabledWhen=(if model.everythingHasBeenSent false true)}} +
{{translate "input"}}
+ {{/link-to}} +
+
\ No newline at end of file diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/App/HtmlTemplates/validation.html b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/HtmlTemplates/validation.html new file mode 100644 index 00000000..67b5ebbe --- /dev/null +++ b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/HtmlTemplates/validation.html @@ -0,0 +1,65 @@ +
+ {{outlet "navigation"}} + + + + + + + + + + + + + + {{#each model.subscriptionDetails as |subscriptionDetail|}} + + {{#if subscriptionDetail.checked}} + + + + + + + + {{else}} + {{! we don't yet know the values of the other fields }} + + + + + + + {{/if}} + + {{/each}} + +
{{translate "event"}}{{translate "name"}}{{translate "subscriptionDetail"}}{{translate "oldValue"}}{{translate "newValue"}}{{translate "status"}}{{translate "fehler"}}
{{subscriptionDetail.event}}{{subscriptionDetail.name}}{{subscriptionDetail.subscriptionDetail}}{{subscriptionDetail.oldValue}}{{subscriptionDetail.newValue}} +
+ {{#if subscriptionDetail.error}} + cancel + {{else}} + check_circle + {{/if}} +
+
{{subscriptionDetail.error}} +
+
+
+
+
+
+ {{#link-to "input" tagName="button" class="btn btn-outline-secondary" disabledWhen=cannotGoBack}} +
{{translate "correct"}}
+ {{/link-to}} +
+
+ {{translate "continueWithErrors"}} +
+
+ {{#link-to "result" tagName="button" class="btn btn-primary" disabledWhen=cannotContinue}} +
{{translate "submit"}}
+ {{/link-to}} +
+
\ No newline at end of file diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/App/HtmlTemplates/vssver2.scc b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/HtmlTemplates/vssver2.scc new file mode 100644 index 00000000..b53ae4eb Binary files /dev/null and b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/HtmlTemplates/vssver2.scc differ diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Routes/index.js b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Routes/index.js new file mode 100644 index 00000000..8ce512db --- /dev/null +++ b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Routes/index.js @@ -0,0 +1,12 @@ +define([ + 'ember', + 'application', + 'translate' +], function (ember, app, translate) { + app.IndexRoute = ember.Route.extend({ + beforeModel: function (transition) { + // always transition to input + this.transitionTo('input'); + } + }); +}); \ No newline at end of file diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Routes/input.js b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Routes/input.js new file mode 100644 index 00000000..d15b2991 --- /dev/null +++ b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Routes/input.js @@ -0,0 +1,48 @@ +define([ + 'ember', + 'application', + 'translate', + 'App/Templates/input', + 'App/Controllers/input' +], function (ember, app, translate) { + app.InputRoute = ember.Route.extend({ + renderTemplate: function () { + this.render(); + + // render the navigation + this.render('navigation', { + into: 'input', + outlet: 'navigation' + }); + }, + + actions: { + didTransition: function () { + var applicationController = this.controllerFor('application'); + + if (applicationController.get('resetInputTextarea') === true) { + applicationController.set('resetInputTextarea', false); + this.controller.set('textareaValue', ''); + } + }, + + willTransition: function (transition) { + if (transition.targetName === 'result') { + // it's not possible to go directly to the result route + transition.abort(); + return; + } + + if (this.controller.get('cannotContinue')) { + transition.abort(); + return; + } + + // put the subScriptiondetails to the application controller so it can be accessed + // by the other routes + var subscriptionDetails = this.controller.get('subscriptionDetails'); + this.controllerFor('application').set('subscriptionDetails', subscriptionDetails); + } + } + }); +}); \ No newline at end of file diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Routes/result.js b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Routes/result.js new file mode 100644 index 00000000..44ea7019 --- /dev/null +++ b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Routes/result.js @@ -0,0 +1,102 @@ +define([ + 'ember', + 'application', + 'api', + 'App/Templates/result' +], function (ember, app, api) { + app.ResultRoute = ember.Route.extend({ + renderTemplate: function () { + this.render(); + + // render the navigation + this.render('navigation', { + into: 'result', + outlet: 'navigation' + }); + }, + + model: function () { + var model = ember.Object.create(); + + // get the subscriptionDetails + var applicationController = this.controllerFor('application'); + var subscriptionDetails = applicationController.get('subscriptionDetails'); + + if (subscriptionDetails === null) { + // there are no subscriptionDetails + this.transitionTo('input'); + return; + } + + // put a reference to the subscriptionDetails object on the model + model.set('subscriptionDetails', subscriptionDetails); + model.set('error',false); + + // put the new subscriptionDetails on the server one after another + subscriptionDetails.reduce(function (previous, item) { + // only send checked items without errors + if (!item.get('checked') || item.get('error') !== undefined) { + return previous; + } + + return previous.then(function () { + var raw = item.get('raw'); + raw.Value = item.get('newValue'); + + item.set('loading', true); + new ember.RSVP.Promise(function (resolve) { + // set the new value of the subscriptionDetail + api.updateSubscriptionDetail(raw, function () { + item.set('uploaded', true); + item.set('loading', false); + resolve(); + }); + ember.Instrumentation.subscribe('validationErrorOccurred',{ + before(name, timestamp, payload) { + var message = payload.responseJSON.Issues[0].Message; + if (payload.status === 409 && message.search(item.subscriptionDetailId) > 0 && message.search('= '+item.newValue) > 0) { + item.set('errorUpload', message); + item.set('uploaded', false); + item.set('loading', false); + model.set('error',true); + + } + }, + after(name, timestamp, payload) { + + } + }); + + }); + + }); + }, ember.RSVP.Promise.resolve()).then(function () { + // every detail has bee sent + model.set('everythingHasBeenSent', true); + }); + + return model; + }, + + actions: { + willTransition: function (transition) { + if (transition.targetName === 'validation') { + // don't allow transitions to validation + transition.abort(); + return; + } + + if (this.controller.get('model.everythingHasBeenSent') !== true) { + // not every detail has been checked + transition.abort(); + } + }, + + didTransition: function () { + // set the resetInputTextarea property on the application controller + // to inform the input route that the textarea can be cleared + this.controllerFor('application').set('resetInputTextarea', true); + } + } + }); +}); \ No newline at end of file diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Routes/validation.js b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Routes/validation.js new file mode 100644 index 00000000..17e91217 --- /dev/null +++ b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Routes/validation.js @@ -0,0 +1,86 @@ +define([ + 'ember', + 'application', + 'App/Controllers/validation', + 'App/Templates/validation', + 'App/Services/validationHelpers', +], function (ember, app) { + app.ValidationRoute = ember.Route.extend({ + renderTemplate: function () { + this.render(); + + // render the navigation + this.render('navigation', { + into: 'validation', + outlet: 'navigation' + }); + }, + + // service + validationHelpers: ember.inject.service('validationHelpers'), + + model: function (params, transition) { + + var model = ember.Object.create(); + + // get the subscriptionDetails + var applicationController = this.controllerFor('application'); + var subscriptionDetails = applicationController.get('subscriptionDetails'); + + if (subscriptionDetails === null) { + // there are no subscriptionDetails + this.transitionTo('input'); + return; + } + + // put a reference to the subscriptionDetails object on the model + model.set('subscriptionDetails', subscriptionDetails); + + // when there are only errors + // there is no reason to continue + var thereAreOnlyErrors = true; + + // cache api responses until + // every row is validated + var cache = { + events: {}, + subscriptionDetails: {}, + names: {} + }; + + var validationHelpers = this.get('validationHelpers'); + + // validate the subscriptionDetails + subscriptionDetails.reduce(function (previous, item) { + return previous.then(function () { + return validationHelpers.validateSubscriptionDetail(cache, item).then(function () { + if (item.get('error') === undefined) { + thereAreOnlyErrors = false; + } + }); + }); + }, ember.RSVP.Promise.resolve()).then(function () { + + // every row has been checked + model.set('everythingIsChecked', true); + model.set('thereAreOnlyErrors', thereAreOnlyErrors); + }); + + return model; + }, + + actions: { + willTransition: function (transition) { + if (transition.targetName === 'result') { + if (this.controller.get('cannotContinue')) { + transition.abort(); + } + } else { + if (this.controller.get('cannotGoBack')) { + transition.abort(); + } + } + } + } + }); +}); \ No newline at end of file diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Routes/vssver2.scc b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Routes/vssver2.scc new file mode 100644 index 00000000..1f5c77cb Binary files /dev/null and b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Routes/vssver2.scc differ diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Services/validationHelpers.js b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Services/validationHelpers.js new file mode 100644 index 00000000..e97d7f9c --- /dev/null +++ b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Services/validationHelpers.js @@ -0,0 +1,203 @@ +define([ + 'ember', + 'application', + 'api', + 'translate', + 'constants' +], function (ember, app, api, translate, constants) { + app.ValidationHelpersService = ember.Service.extend({ + // returns true if value is a possible value for type + // this could be extended to work with more datatypes + checkDatatype: function (type, value) { + switch (type) { + case constants.vssType.IntField: + return /^\d+$/.test(value); + + case constants.vssType.Text: + return true; + + case constants.vssType.MemoText: + return true; + + case constants.vssType.Currency: + return !isNaN(value); + + default: + return false; + } + }, + + // this function will be able to use the + // api.ember.getPerson api function when it gets implemented + getName: function (personId) { + + return api.ember.getPerson(personId).then(function(data){ + return data.LastName + ' ' + data.FirstName; + }); + + }, + + // this function validates a subscriptionDetail and translates ids into strings + // (e.g. personId 12345 => 'John Doe') + // in case there is an error the error property is set + // a promise is returned + validateSubscriptionDetail: function (cache, subscriptionDetail) { + + if (subscriptionDetail.get('checked') === true) { + // this item has already been checked + return ember.RSVP.Promise.resolve(); + } + + var that = this; + + var personId = subscriptionDetail.get('personId'); + var eventId = subscriptionDetail.get('eventId'); + var subscriptionDetailId = subscriptionDetail.get('subscriptionDetailId'); + var newValue = subscriptionDetail.get('newValue'); + + + return new ember.RSVP.Promise(function (resolve, reject) { + + // Step 1: check if the event exists + if (cache.events[eventId] !== undefined) { + resolve(cache.events[eventId]); + return; + } + + api.ember.getEvent(eventId).then(function (response) { + cache.events[eventId] = response; + resolve(response); + }); + + }).then(function (response) { + if (response == null) { + subscriptionDetail.set('error', translate.getString('eventDoesNotExist')); + throw Error('event does not exist'); + } else if (response.hasError) { + subscriptionDetail.set('error', translate.getString('eventDoesNotExist')); + throw Error('event does not exist'); + } + + subscriptionDetail.set('event', response.Number); + + eventPersonId = eventId + '_' + personId + // Step 2: get the subscription for the event + if (cache.subscriptionDetails[eventPersonId] !== undefined) { + + if(cache.subscriptionDetails[eventPersonId].name == undefined) { + that.getName(personId).then(function (personResponse) { + subscriptionDetail.set('name', personResponse); + }); + } + return cache.subscriptionDetails[eventPersonId]; + } + + return api.ember.getSubscriptionByIdEventPerson(eventId, personId).then(function (response) { + + if (response.length === 0) { + // the student does not exist or is not subscribed to the event + subscriptionDetail.set('error', translate.getString('noSubscription')); + throw Error('student is not subscribed to the event'); + } else { + cache.subscriptionDetails[eventPersonId] = response; + // Step 3: get the name of the person + that.getName(personId).then(function (personResponse) { + subscriptionDetail.set('name', personResponse); + }); + } + + return response; + }); + + }).then(function (response) { + + var idSubscription = response[0].IdSubscription; + if (cache.subscriptionDetails[idSubscription] !== undefined) { + return cache.subscriptionDetails[idSubscription]; + } + + return api.ember.SubscriptionDetailsByIdSubscription(idSubscription).then(function (response) { + cache.subscriptionDetails[idSubscription] = response; + return response; + }); + + }).then(function (response) { + var subscriptionDetailsByEvent = {}; + + // transform the response into the following format: + // { + // : { + // name : string, + // students: { + // : { + // value : string, + // type : int, (dataType) + // raw : object (returned by api + // }, + // ... + // } + // }, + // ... + // } + response.forEach(function (item) { + if (subscriptionDetailsByEvent[item.IdAnmeldeVSS] === undefined) { + subscriptionDetailsByEvent[item.IdAnmeldeVSS] = { + name: item.VssBezeichnung, + students: {} + }; + } + + subscriptionDetailsByEvent[item.IdAnmeldeVSS].students[item.IdPerson] = { + value: item.Value, + type: item.VssTypEx, + raw: item + }; + }); + + var subscriptionDetailByEvent = subscriptionDetailsByEvent[subscriptionDetailId]; + + if (subscriptionDetailByEvent === undefined) { + // the event doesn't have that subscriptionDetail + subscriptionDetail.set('error', translate.getString('subscriptionDetailDoesNotExist')); + throw Error('subscriptionDetail does not exist'); + } + + var subscriptionDetailName = subscriptionDetailByEvent.name; + subscriptionDetail.set('subscriptionDetail', subscriptionDetailName); + + var subscriptionDetailForStudent = subscriptionDetailByEvent.students[personId]; + + var raw = subscriptionDetailForStudent.raw; + + if (raw.VssStyle !== 'TX' || raw.VssInternet !== 'E' && ( + raw.VssTypEx !== constants.vssType.IntField || + raw.VssTypEx !== constants.vssType.Currency || + raw.VssTypEx !== constants.vssType.Text || + raw.VssTypEx !== constants.vssType.MemoText + )) { + // the subscriptiondetails is not (yet) meant to be edited on the internet + subscriptionDetail.set('error', translate.getString('notEditable')); + throw Error('subscriptionDetail not editable'); + } + + subscriptionDetail.set('oldValue', subscriptionDetailForStudent.value); + subscriptionDetail.set('raw', subscriptionDetailForStudent.raw); + + if (!that.checkDatatype(subscriptionDetailForStudent.type, newValue)) { + // the new value is invalid + subscriptionDetail.set('error', translate.getString('invalidSubscriptionDetailValue')); + throw Error('the new value is invalid'); + } + + + + + }).catch(ember.K /* ignore rejections */).then(function () { + + // done + subscriptionDetail.set('checked', true); + + }); + } + }); +}); \ No newline at end of file diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Services/vssver2.scc b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Services/vssver2.scc new file mode 100644 index 00000000..a6953efd Binary files /dev/null and b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Services/vssver2.scc differ diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Templates/application.js b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Templates/application.js new file mode 100644 index 00000000..07b29344 --- /dev/null +++ b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Templates/application.js @@ -0,0 +1,3 @@ +define(['ember', 'text!App/HtmlTemplates/application.html'], function (ember, applicationTemplate) { + ember.TEMPLATES['application'] = ember.Handlebars.compile(applicationTemplate); +}); \ No newline at end of file diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Templates/input.js b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Templates/input.js new file mode 100644 index 00000000..36edb34c --- /dev/null +++ b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Templates/input.js @@ -0,0 +1,3 @@ +define(['ember', 'text!App/HtmlTemplates/input.html'], function (ember, inputTemplate) { + ember.TEMPLATES['input'] = ember.Handlebars.compile(inputTemplate); +}); \ No newline at end of file diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Templates/navigation.js b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Templates/navigation.js new file mode 100644 index 00000000..20270c3d --- /dev/null +++ b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Templates/navigation.js @@ -0,0 +1,3 @@ +define(['ember', 'text!App/HtmlTemplates/navigation.html'], function (ember, navigationTemplate) { + ember.TEMPLATES['navigation'] = ember.Handlebars.compile(navigationTemplate); +}); \ No newline at end of file diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Templates/result.js b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Templates/result.js new file mode 100644 index 00000000..5ddd698f --- /dev/null +++ b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Templates/result.js @@ -0,0 +1,3 @@ +define(['ember', 'text!App/HtmlTemplates/result.html'], function (ember, resultTemplate) { + ember.TEMPLATES['result'] = ember.Handlebars.compile(resultTemplate); +}); \ No newline at end of file diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Templates/validation.js b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Templates/validation.js new file mode 100644 index 00000000..de336e0b --- /dev/null +++ b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Templates/validation.js @@ -0,0 +1,3 @@ +define(['ember', 'text!App/HtmlTemplates/validation.html'], function (ember, validationTemplate) { + ember.TEMPLATES['validation'] = ember.Handlebars.compile(validationTemplate); +}); \ No newline at end of file diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Templates/vssver2.scc b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Templates/vssver2.scc new file mode 100644 index 00000000..5ba66f97 Binary files /dev/null and b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/Templates/vssver2.scc differ diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/App/router.js b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/router.js new file mode 100644 index 00000000..e7d3c8fc --- /dev/null +++ b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/router.js @@ -0,0 +1,25 @@ +define([ + 'ember', + 'application', + + // load the application template and controller + // the application route gets created by the CLX framework + 'App/Controllers/application', + 'App/Templates/application', + + // load the navigation template + 'App/Templates/navigation', + + // load all the routes + 'App/Routes/index', + 'App/Routes/input', + 'App/Routes/validation', + 'App/Routes/result' +], function (ember, app) { + app.Router = ember.Router.extend(); + app.Router.map(function () { + this.route('input'); + this.route('validation'); + this.route('result'); + }); +}); \ No newline at end of file diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/App/vssver2.scc b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/vssver2.scc new file mode 100644 index 00000000..d74dc1f2 Binary files /dev/null and b/test/apps/EmberApps/AnmeldedetailsEinlesen/App/vssver2.scc differ diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/index.html b/test/apps/EmberApps/AnmeldedetailsEinlesen/index.html new file mode 100644 index 00000000..29c3677f --- /dev/null +++ b/test/apps/EmberApps/AnmeldedetailsEinlesen/index.html @@ -0,0 +1,7 @@ + + + + +
+ + diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/main.js b/test/apps/EmberApps/AnmeldedetailsEinlesen/main.js new file mode 100644 index 00000000..dbe90696 --- /dev/null +++ b/test/apps/EmberApps/AnmeldedetailsEinlesen/main.js @@ -0,0 +1,50 @@ +(function (root) { + require(['../config', '../customConfig'], function (mainConfig, customConfig) { + requirejs.config(mainConfig); + requirejs.config(customConfig); + require([ + 'application', + 'ember', + 'framework', + 'applicationHelpers', + 'api', + 'locale/de-CH', + 'locale/Custom/de-CH', + 'locale/fr-CH', + 'locale/Custom/fr-CH', + 'appConfig', + 'storage', + 'App/router' + ], function (app, ember, framework, applicationHelpers, api, de_CH, custom_de_CH, fr_CH, custom_fr_CH, appConfig, storage) { + + // templates are all loaded when the application starts + // so this is not needed + app.automaticTemplateLoading = false; + + // change api a little so the callback gets called + api.ember.getEvent = function (id) { + return api.getEmber('Events/' + id, true); + }; + + api.ember.getSubscriptionByIdEventPerson = function (idEvent, idPerson) { + return api.getEmber('Subscriptions/?filter.EventId==' + idEvent + '&filter.PersonId==' + idPerson, true); + }; + + api.ember.SubscriptionDetailsByIdSubscription = function (idSubscription) { + return api.getEmber('Subscriptions/'+ idSubscription +'/SubscriptionDetails', true); + }; + + // patch langauge files + ember.$.extend(de_CH, custom_de_CH); + ember.$.extend(fr_CH, custom_fr_CH); + + // start application + applicationHelpers.initializeApplication(function () { + var appName = 'ClxApp'; + var application = ember.Application.create(app); + root[appName] = application; + applicationHelpers.afterStartApplication(); + }); + }); + }); +})(this); \ No newline at end of file diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/settings.js b/test/apps/EmberApps/AnmeldedetailsEinlesen/settings.js new file mode 100644 index 00000000..0005307d --- /dev/null +++ b/test/apps/EmberApps/AnmeldedetailsEinlesen/settings.js @@ -0,0 +1,12 @@ +define([ + 'ember', + 'main_settings' +], function (ember, mainSettings) { + var settingsClass = ember.Object.extend(mainSettings); + var settings = { + // module specific settings + applicationScope: 'NG',//constants.applicationScope.tutoring, + }; + var inheritedSettingsClass = settingsClass.extend(settings); + return inheritedSettingsClass.create(); +}); \ No newline at end of file diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/uiSettings.js b/test/apps/EmberApps/AnmeldedetailsEinlesen/uiSettings.js new file mode 100644 index 00000000..8e466558 --- /dev/null +++ b/test/apps/EmberApps/AnmeldedetailsEinlesen/uiSettings.js @@ -0,0 +1,11 @@ +define([ + 'ember', + 'main_uiSettings' +], function (ember, mainUiSettings) { + var uiSettingsClass = ember.Object.extend(mainUiSettings); + var uiSettings = { + // module specific settings + }; + var inheritedUiSettingsClass = uiSettingsClass.extend(uiSettings); + return inheritedUiSettingsClass.create(); +}); \ No newline at end of file diff --git a/test/apps/EmberApps/AnmeldedetailsEinlesen/vssver2.scc b/test/apps/EmberApps/AnmeldedetailsEinlesen/vssver2.scc new file mode 100644 index 00000000..5977d905 Binary files /dev/null and b/test/apps/EmberApps/AnmeldedetailsEinlesen/vssver2.scc differ diff --git a/test/apps/EmberApps/App/Components/accordionNavigationComponent.js b/test/apps/EmberApps/App/Components/accordionNavigationComponent.js new file mode 100644 index 00000000..ba259edd --- /dev/null +++ b/test/apps/EmberApps/App/Components/accordionNavigationComponent.js @@ -0,0 +1,86 @@ +define([ + 'application', + 'jquery', + 'ember', + 'uiSettings' +], function (app, $, ember, uiSettings) { + app.AccordionNavigationComponent = ember.Component.extend({ + tagName: 'div', + classNames: ['accordionNavigation'], + + didInsertElement: function () { + var element = this.$(); + var that = this; + ember.run.scheduleOnce('afterRender', + function() { + // init click behavior + element.find('a') + .click(function(e) { + var link = $(this); + if (!that.get('forceOpenActive')) + that.openNewNavigation(link); + + // in any case prevent the link-to action, because the content of the navigation might be something else already + if (link.hasClass('active')) { + e.preventDefault(); + return false; + } + }); + + // open active link on page visit + element.find('a.active').next('.navigationContent').slideDown(uiSettings.defaultAnimationSpeed); + }); + }, + + forceOpenActiveTriggered: ember.observer('forceOpenActive', + function () { + var that = this; + ember.run.scheduleOnce('afterRender', + function() { + var link = that.$().find('a.active'); + if (link.length === 1) + that.openNewNavigation(link, true); + }); + }), + + openNewNavigation: function (link, forced) { + var container = link.next('.navigationContent'); + // first close all open containers (should always be just one) and then open a new one + var isVisible = container.is(':visible'); + var isActive = link.hasClass('active'); + var openContainers = $('.navigationContent:visible').toArray(); + var time = 1; + if (openContainers.length > 0) { + $.each(openContainers, + function () { + if (this !== container[0]) { + time = uiSettings.defaultAnimationSpeed; + $(this).slideUp(uiSettings.defaultAnimationSpeed); + } + }); + } + + if (!isVisible || !isActive || forced) { + setTimeout(function() { + container.slideDown(uiSettings.defaultAnimationSpeed); + }, time); + } + }, + + openNavigationTriggered: ember.observer('openNavigation', + function () { + var that = this; + ember.run.scheduleOnce('afterRender', + function() { + var index = that.get('openNavigation'); + if (index > -1) { + var link = that.$().find('a').eq(index); + if (!link.hasClass('active')) { + that.openNewNavigation(link); + } + } + }); + + }).on('init') + }); +}); \ No newline at end of file diff --git a/test/apps/EmberApps/App/Components/adaptableTextBoxComponent.js b/test/apps/EmberApps/App/Components/adaptableTextBoxComponent.js new file mode 100644 index 00000000..04b91ca8 --- /dev/null +++ b/test/apps/EmberApps/App/Components/adaptableTextBoxComponent.js @@ -0,0 +1,22 @@ +define([ + 'application', + 'jquery', + 'ember', + 'guiHelpers' +], + function(app, $, ember, guiHelpers) { + app.AdaptableTextBoxComponent = ember.Component.extend($.extend({}, + guiHelpers.adaptableTextBoxComponentLogic, + { + tagName: 'div', + classNames: ['textBox'], + didInsertElement: function() { + var element = this.$(); + this.initializeInputControl(element); + }, + updateControlModel: function(value) { + this.set('value', (value || '').trim()); + } + } + )); + }); \ No newline at end of file diff --git a/test/apps/EmberApps/App/Components/addressLabelComponent.js b/test/apps/EmberApps/App/Components/addressLabelComponent.js new file mode 100644 index 00000000..2a462db8 --- /dev/null +++ b/test/apps/EmberApps/App/Components/addressLabelComponent.js @@ -0,0 +1,39 @@ +define([ + 'jquery', + 'ember' +], function ($, ember) { + ClxApp.AddressLabelComponent = ember.Component.extend({ + tagName: 'pre', + classNames: ['address'], + + didInsertElement: function () { + this.$().text(this.getText()); + }, + + getText: function() { + var address = this.get('address'); + var text = ''; + if (address.Company) { + text += address.Company; + text += '\r\n'; + } + if (address.Department) { + text += address.Department; + text += '\r\n'; + } + text += address.FormOfAddress + ' ' + (address.FirstName ? address.FirstName + ' ': '') + address.LastName; + text += '\r\n'; + text += address.AddressLine1; + text += '\r\n'; + if (address.AddressLine2) { + text += address.AddressLine2; + text += '\r\n'; + } + text += address.Zip + ' ' + address.Location; + text += '\r\n'; + text += address.Country; + + return text; + } + }); +}); \ No newline at end of file diff --git a/test/apps/EmberApps/App/Components/autoScrollContainerComponent.js b/test/apps/EmberApps/App/Components/autoScrollContainerComponent.js new file mode 100644 index 00000000..e9057e0c --- /dev/null +++ b/test/apps/EmberApps/App/Components/autoScrollContainerComponent.js @@ -0,0 +1,76 @@ +define([ + 'jquery', + 'ember', + 'uiSettings' +], function ($, ember, uiSettings) { + ClxApp.AutoScrollContainerComponent = ember.Component.extend({ + tagName: 'div', + classNames: ['autoScrollContainer'], + + dataCollectionChanged: ember.observer('dataCollection', + function() { + var dataCollection = this.get('dataCollection'); + if (dataCollection && (dataCollection.length === undefined || typeof dataCollection === 'string')) + console + .error('auto-save-controller: if "dataCollection" is set, it must be an Array or an equivalent ember object'); + }).on('init'), + + underlyingDataCollectionChanged: ember.observer('dataCollection.@each', + function() { + var that = this; + ember.run.scheduleOnce('afterRender', + function () { + if (that.get('waitForOtherAnimation')) + setTimeout(function() { + that.checkScrolling(); + }, uiSettings.defaultAnimationSpeed + 100); + else + that.checkScrolling(); + }); + }), + + activeElementIdChanged: ember.observer('activeElementId', function() { + var that = this; + ember.run.scheduleOnce('afterRender', function () { + if (that.get('waitForOtherAnimation')) + setTimeout(function() { + that.scrollTo(that.get('activeElementId')); + }, uiSettings.defaultAnimationSpeed + 100); + else + that.scrollTo(that.get('activeElementId')); + }); + }), + + checkScrolling: function() { + var element = this.$(); + var last = element.children().last(); + var height = element.outerHeight(); + var top = last.offset().top; + var bottom = top + last.outerHeight(); + var elementTop = element.offset().top; + if (bottom > height) // hangs out lower bound + this.scrollTo(undefined, top); + if (top < elementTop) // hangs out upper bound + this.scrollTo(undefined, top); + }, + + scrollTo: function (idElement, elementTop) { + var container = this.$(); + var containerTop = container.offset().top; + if (elementTop === undefined) { + if (!idElement) + return; + var el = $('#' + idElement); + if (el.length > 0) + elementTop = el.eq(0).offset().top; + } + + var scrollTop = container.scrollTop() - containerTop + elementTop; + container + .animate({ + scrollTop: scrollTop + }, + uiSettings.defaultAnimationSpeed); + } + }); +}); \ No newline at end of file diff --git a/test/apps/EmberApps/App/Components/blendElementComponent.js b/test/apps/EmberApps/App/Components/blendElementComponent.js new file mode 100644 index 00000000..ae503023 --- /dev/null +++ b/test/apps/EmberApps/App/Components/blendElementComponent.js @@ -0,0 +1,90 @@ +define([ + 'application', + 'jquery', + 'ember', + 'uiSettings' +], function (app, $, ember, uiSettings) { + app.BlendElementComponent = ember.Component.extend({ + tagName: 'div', + classNames: ['blendElement'], + classNameBindings: ['blendType'], + + validBlendTypes: ['fadeIn', 'slideIn', 'slideDown'], + + initialize: function() { + var blendType = this.get('blendType'); + if (!blendType) { + blendType = uiSettings.defaultBlendType; + if (!blendType) + blendType = 'fadeIn'; + this.set('blendType', blendType); + } + if ($.inArray(blendType, this.validBlendTypes) === -1) { + console.error('blend-element: blend type "' + blendType + '" is not valid. Valid are: ' + this.validBlendTypes.join(', ')); + } + }.on('init'), + + didInsertElement: function () { + var that = this; + var element = this.$(); + + if (this.get('waitForOtherAnimation')) + element.hide(); + ember.run.scheduleOnce('afterRender', function () { + if (that.get('waitForOtherAnimation')) { + setTimeout(function() { + element.show(); + setTimeout(function() { + that.applyEffect(element, true); + }, + 10); + }, + uiSettings.defaultAnimationSpeed); + } else + setTimeout(function () { + if (!that.get('isDestroying') && ! that.get('isDestroyed')) + that.applyEffect(element, true); + }, 10); + }); + }, + + willDestroyElement: function () { + var that = this; + var element = this.$(); + var clone = element.clone(); + var prev = element.prev().eq(0); + var parent = element.parent(); + + this.$().hide(); + if (prev.length === 0) + parent.append(clone); + else + prev.after(clone); + + setTimeout(function () { + that.applyEffect(clone, false); + }, 10); + setTimeout(function () { + clone.remove(); + }, uiSettings.defaultAnimationSpeed); + }, + + applyEffect: function (element, show) { + var blendType = this.get('blendType'); + switch (blendType) { + case 'slideDown': + if (show) + element.slideDown(uiSettings.defaultAnimationSpeed); + else + element.slideUp(uiSettings.defaultAnimationSpeed); + break; + default: + if (show) + element.removeClass(blendType); + else + element.addClass(blendType); + break; + } + } + }); +}); \ No newline at end of file diff --git a/test/apps/EmberApps/App/Components/comboBoxComponent.js b/test/apps/EmberApps/App/Components/comboBoxComponent.js new file mode 100644 index 00000000..9e408135 --- /dev/null +++ b/test/apps/EmberApps/App/Components/comboBoxComponent.js @@ -0,0 +1,549 @@ +define([ + 'application', + 'jquery', + 'ember', + 'keyboard', + 'actionStack', + 'icons', + 'translate', + 'guiHelpers', + 'regexHelpers', + 'uiSettings' +], function (app, $, ember, keyboard, actionStack, icons, translate, guiHelpers, regexHelpers, uiSettings) { + app.ComboBoxComponent = ember.Component.extend($.extend({}, guiHelpers.adaptableTextBoxComponentLogic, { + tagName: 'div', + classNames: ['combobox'], + + didInsertElement: function() { + this.set('lastValidSearch', ''); + this.set('lastConfirmedValue', ''); + this.set('lastFilteredSource', undefined); + this.set('isPopupVisible', false); + this.set('didActionHappen', false); + this.set('keepPopupOpen', false); + this.set('isAllVisible', false); + + // run in another thread to save performance and let ember finish rendering the view + var that = this; + var element = this.$(); + element.prop('autocomplete', false); + setTimeout(function () { + try { + var source = that.get('source'); + if (source) + that.fillBySource(source); + + var dataSource = that.get('dataSource'); + + var inputControl = that.get('adaptAs') + ? that.createAdaptableControl() + : that.createInputControl(); + element.append(inputControl); + + var value = that.get('value'); + if (value) { + var item = dataSource.find(function(el) { + return el.Key === value; + }); + if (item || that.get('allowArbitraryText')) { + if (item) + value = item.Value; + that.setInputControlContent(value); + that.set('lastValidSearch', value); + that.set('lastConfirmedValue', value); + } + } + + if (!dataSource) { + console.error('combobox ' + that.$().attr('id') + ' cannot evaluate data source'); + return; + } + + element.append(that.createShowAllButton()); + var popup = that.buildPopup(dataSource); + popup.hide(); + element.append(popup); + } catch (ex) { + // don't throw the error when the object is beeing destroyed + if (!that.get('isDestroyed') && !that.get('isDestroying')) + throw ex; + } + }, 1); + }, + + fillBySource: function(source) { + var keyName = this.get('keyName'); + var valueName = this.get('valueName'); + if (!keyName || !valueName) { + console.error('when using "source" on combobox, "keyName" and "valueName" must be set as well'); + return; + } + var dataSource = []; + $.each(source, + function() { + dataSource.push({ + Key: this[keyName], + Value: this[valueName] + }); + }); + this.set('dataSource', dataSource); + }, + + createInputControl: function() { + var input = guiHelpers.getTextbox(undefined, this.get('id'), true); + input.attr('autocomplete', 'off'); + if (this.get('inputMaxlength')) + input.prop('maxlength', this.get('inputMaxlength')); + + guiHelpers.addLongtextTooltip(input); + + return this.initInputControl(input); + }, + + createAdaptableControl: function() { + var editDiv = guiHelpers.getDiv(); + + this.initializeInputControl(editDiv, true); + return this.initInputControl(editDiv); + }, + + initInputControl: function(control) { + control.prop('disabled', this.get('disabled')); + control.prop('name', this.get('name')); + + var that = this; + control.on('keyup', + function(e) { + that.handleKeyUp(e); + }) + .on('keydown', + function(e) { + that.handleKeyDown(e); + }) + .on('blur', + function() { + that.handleBlur(); + }); + + this.set('inputControl', control); + return control; + }, + + getAdaptation: function() { + return this.get(this.adaptationAttribute); + }, + + endsWith: function (text, suffix) { + return text.indexOf(suffix, text.length - suffix.length) > -1; + }, + + getInputControlContent: function() { + var inputCtrl = this.get('inputControl'); + return this.isAdaptableControl() // empty innerText returns '\n' + ? inputCtrl.prop('innerText').replace(/\n$/, this.endsWith(inputCtrl.html(), '
') ? ' ' : '') + : inputCtrl.val(); + }, + + setInputControlContent: function(content) { + var inputCtrl = this.get('inputControl'); + this.isAdaptableControl() + ? inputCtrl.prop('innerText', (this.isAdaptableControl('text') ? this.textControlContentTransformer(content) : content)) + : inputCtrl.val(content); + }, + + updateControlModel: function(valueKey) { + if (this.get('value') === valueKey) return; + if (valueKey && valueKey.trim) + valueKey = valueKey.trim(); + this.set('value', valueKey); + }, + + createShowAllButton: function() { + var that = this; + var button = guiHelpers.getButton(icons.dropdown, null, guiHelpers.classes.controls.inputButton) + .attr('tabIndex', -1) + .attr('title', translate.getString('showAll')) + .prop('disabled', this.get('disabled')) + .on('mousedown', + function() { + if (!that.get('isAllVisible')) { + that.set('isAllVisible', true); + that.set('keepPopupOpen', true); + var searchText = that.getInputControlContent(); + var regex = searchText + ? new RegExp(regexHelpers + .escapeRegExp(searchText), + 'gi') + : undefined; + that.rebuildPopup(that.get('dataSource'), regex); + } else { + that.hidePopup(); + } + }) + .on('blur', + function () { + that.set('keepPopupOpen', false); + that.handleBlur(); + }); + this.set('showAllButton', button); + return button; + }, + + buildPopup: function(dataSource, searchRegex) { + var that = this; + var popup = $('