├── .gitignore
├── jsconfig.json
├── LICENSE
├── src
├── html
│ ├── PropertyEditorGrid.html
│ └── TranslationGrid.html
└── js
│ ├── PropertyEditor
│ ├── EntityPropertyHandler.js
│ ├── AttributePropertyHandler.js
│ └── XrmPropertyEditor.js
│ └── Translator
│ ├── EntityHandler.js
│ ├── ChartHandler.js
│ ├── ViewHandler.js
│ ├── AttributeHandler.js
│ ├── FormMetaHandler.js
│ ├── ContentSnippetHandler.js
│ ├── OptionSetHandler.js
│ ├── WebResourceHandler.js
│ ├── FormHandler.js
│ ├── TranslationHandler.js
│ └── XrmTranslator.js
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | Publish/*
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6"
4 | }
5 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Florian Krönert
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/html/PropertyEditorGrid.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Xrm-Property-Editor
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/html/TranslationGrid.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Xrm-Easy-Translation
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/js/PropertyEditor/EntityPropertyHandler.js:
--------------------------------------------------------------------------------
1 | /* @preserve
2 | * MIT License
3 | *
4 | * Copyright (c) 2017 Florian Krönert
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | */
25 | (function (EntityPropertyHandler, undefined) {
26 | "use strict";
27 |
28 | function ApplyChanges(changes, labels) {
29 | for (var change in changes) {
30 | if (!changes.hasOwnProperty(change)) {
31 | continue;
32 | }
33 |
34 | for (var i = 0; i < labels.length; i++) {
35 | var label = labels[i];
36 |
37 | if (label.LanguageCode == change) {
38 | label.Label = changes[change];
39 | label.HasChanged = true;
40 |
41 | break;
42 | }
43 |
44 | // Did not find label for this language
45 | if (i === labels.length - 1) {
46 | labels.push({ LanguageCode: change, Label: changes[change] })
47 | }
48 | }
49 | }
50 | }
51 |
52 | function GetUpdates() {
53 | var records = XrmPropertyEditor.GetGrid().records;
54 |
55 | var update = XrmPropertyEditor.metadata;
56 |
57 | for (var i = 0; i < records.length; i++) {
58 | var record = records[i];
59 |
60 | if (record.w2ui && record.w2ui.changes) {
61 | var labels = null;
62 |
63 | if (record.schemaName === "Display Name") {
64 | labels = update.DisplayName.LocalizedLabels;
65 | } else if (record.schemaName === "Collection Name") {
66 | labels = update.DisplayCollectionName.LocalizedLabels;
67 | }
68 |
69 | var changes = record.w2ui.changes;
70 |
71 | ApplyChanges(changes, labels);
72 | }
73 | }
74 |
75 | return update;
76 | }
77 |
78 | function FillTable () {
79 | var grid = XrmPropertyEditor.GetGrid();
80 | grid.clear();
81 |
82 | var records = [];
83 |
84 | var entity = XrmPropertyEditor.metadata;
85 |
86 | var displayNames = entity.DisplayName.LocalizedLabels;
87 | var collectionNames = entity.DisplayCollectionName.LocalizedLabels;
88 |
89 | if (!displayNames && !collectionNames) {
90 | return;
91 | }
92 |
93 | var singular = {
94 | recid: XrmPropertyEditor.metadata.MetadataId + "|1",
95 | schemaName: "Display Name"
96 | };
97 |
98 | var plural = {
99 | recid: XrmPropertyEditor.metadata.MetadataId + "|2",
100 | schemaName: "Collection Name"
101 | };
102 |
103 | for (var i = 0; i < displayNames.length; i++) {
104 | var displayName = displayNames[i];
105 |
106 | singular[displayName.LanguageCode.toString()] = displayName.Label;
107 | }
108 |
109 | for (var j = 0; j < collectionNames.length; j++) {
110 | var collectionName = collectionNames[j];
111 |
112 | plural[collectionName.LanguageCode.toString()] = collectionName.Label;
113 | }
114 |
115 | records.push(singular);
116 | records.push(plural);
117 |
118 | grid.add(records);
119 | grid.unlock();
120 | }
121 |
122 | EntityPropertyHandler.Load = function() {
123 | var entityName = XrmPropertyEditor.GetEntity();
124 | var entityMetadataId = XrmPropertyEditor.entityMetadata[entityName];
125 |
126 | var request = {
127 | entityName: "EntityDefinition",
128 | entityId: entityMetadataId
129 | };
130 |
131 | WebApiClient.Retrieve(request)
132 | .then(function(response) {
133 | XrmPropertyEditor.metadata = response;
134 |
135 | FillTable();
136 | })
137 | .catch(XrmPropertyEditor.errorHandler);
138 | }
139 |
140 | EntityPropertyHandler.Save = function() {
141 | XrmPropertyEditor.LockGrid("Saving");
142 |
143 | var updates = GetUpdates();
144 | var entityUrl = WebApiClient.GetApiUrl() + "EntityDefinitions(" + XrmPropertyEditor.GetEntityId() + ")";
145 |
146 | WebApiClient.SendRequest("PUT", entityUrl, updates, [{key: "MSCRM.MergeLabels", value: "true"}])
147 | .then(function (response){
148 | XrmPropertyEditor.LockGrid("Publishing");
149 |
150 | return XrmPropertyEditor.Publish();
151 | })
152 | .then(function (response) {
153 | XrmPropertyEditor.LockGrid("Reloading");
154 |
155 | return EntityPropertyHandler.Load();
156 | })
157 | .catch(XrmPropertyEditor.errorHandler);
158 | }
159 | } (window.EntityPropertyHandler = window.EntityPropertyHandler || {}));
--------------------------------------------------------------------------------
/src/js/Translator/EntityHandler.js:
--------------------------------------------------------------------------------
1 | /* @preserve
2 | * MIT License
3 | *
4 | * Copyright (c) 2017 Florian Krönert
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | */
25 | (function (EntityHandler, undefined) {
26 | "use strict";
27 |
28 | function ApplyChanges(changes, labels) {
29 | for (var change in changes) {
30 | if (!changes.hasOwnProperty(change)) {
31 | continue;
32 | }
33 |
34 | // Skip empty labels
35 | if (!changes[change]) {
36 | continue;
37 | }
38 |
39 | for (var i = 0; i < labels.length; i++) {
40 | var label = labels[i];
41 |
42 | if (label.LanguageCode == change) {
43 | label.Label = changes[change];
44 | label.HasChanged = true;
45 |
46 | break;
47 | }
48 |
49 | // Did not find label for this language
50 | if (i === labels.length - 1) {
51 | labels.push({ LanguageCode: change, Label: changes[change] })
52 | }
53 | }
54 | }
55 | }
56 |
57 | function GetUpdates() {
58 | var records = XrmTranslator.GetGrid().records;
59 |
60 | var update = XrmTranslator.metadata;
61 |
62 | for (var i = 0; i < records.length; i++) {
63 | var record = records[i];
64 |
65 | if (record.w2ui && record.w2ui.changes) {
66 | var labels = null;
67 |
68 | if (record.schemaName === "Display Name") {
69 | labels = update[XrmTranslator.GetComponent()].LocalizedLabels;
70 | } else if (record.schemaName === "Collection Name") {
71 | labels = update.DisplayCollectionName.LocalizedLabels;
72 | }
73 |
74 | var changes = record.w2ui.changes;
75 |
76 | ApplyChanges(changes, labels);
77 | }
78 | }
79 |
80 | return update;
81 | }
82 |
83 | function FillTable () {
84 | var grid = XrmTranslator.GetGrid();
85 | grid.clear();
86 |
87 | var records = [];
88 |
89 | var entity = XrmTranslator.metadata;
90 |
91 | var displayNames = entity[XrmTranslator.GetComponent()].LocalizedLabels;
92 | var collectionNames = entity.DisplayCollectionName.LocalizedLabels;
93 |
94 | if (!displayNames && !collectionNames) {
95 | return;
96 | }
97 |
98 | var singular = {
99 | recid: XrmTranslator.metadata.MetadataId + "|1",
100 | schemaName: "Display Name"
101 | };
102 |
103 | var plural = {
104 | recid: XrmTranslator.metadata.MetadataId + "|2",
105 | schemaName: "Collection Name"
106 | };
107 |
108 | for (var i = 0; i < displayNames.length; i++) {
109 | var displayName = displayNames[i];
110 |
111 | singular[displayName.LanguageCode.toString()] = displayName.Label;
112 | }
113 |
114 | for (var j = 0; j < collectionNames.length; j++) {
115 | var collectionName = collectionNames[j];
116 |
117 | plural[collectionName.LanguageCode.toString()] = collectionName.Label;
118 | }
119 |
120 | records.push(singular);
121 | records.push(plural);
122 |
123 | XrmTranslator.AddSummary(records);
124 | grid.add(records);
125 | grid.unlock();
126 | }
127 |
128 | EntityHandler.Load = function() {
129 | var entityName = XrmTranslator.GetEntity();
130 | var entityMetadataId = XrmTranslator.entityMetadata[entityName];
131 |
132 | var request = {
133 | entityName: "EntityDefinition",
134 | entityId: entityMetadataId
135 | };
136 |
137 | return WebApiClient.Retrieve(request)
138 | .then(function(response) {
139 | XrmTranslator.metadata = response;
140 |
141 | FillTable();
142 | })
143 | .catch(XrmTranslator.errorHandler);
144 | }
145 |
146 | EntityHandler.Save = function() {
147 | XrmTranslator.LockGrid("Saving");
148 |
149 | var updates = GetUpdates();
150 | var entityUrl = WebApiClient.GetApiUrl() + "EntityDefinitions(" + XrmTranslator.GetEntityId() + ")";
151 |
152 | return WebApiClient.SendRequest("PUT", entityUrl, updates, [{key: "MSCRM.MergeLabels", value: "true"}])
153 | .then(function (response){
154 | XrmTranslator.LockGrid("Publishing");
155 |
156 | return XrmTranslator.Publish();
157 | })
158 | .then(function(response) {
159 | return XrmTranslator.AddToSolution([XrmTranslator.GetEntityId()], XrmTranslator.ComponentType.Entity);
160 | })
161 | .then(function(response) {
162 | return XrmTranslator.ReleaseLockAndPrompt();
163 | })
164 | .then(function (response) {
165 | XrmTranslator.LockGrid("Reloading");
166 |
167 | return EntityHandler.Load();
168 | })
169 | .catch(XrmTranslator.errorHandler);
170 | }
171 | } (window.EntityHandler = window.EntityHandler || {}));
172 |
--------------------------------------------------------------------------------
/src/js/Translator/ChartHandler.js:
--------------------------------------------------------------------------------
1 | (function (ChartHandler, undefined) {
2 | "use strict";
3 |
4 | function ApplyChanges(changes, labels) {
5 | for (var change in changes) {
6 | if (!changes.hasOwnProperty(change)) {
7 | continue;
8 | }
9 |
10 | // Skip empty labels
11 | if (!changes[change]) {
12 | continue;
13 | }
14 |
15 | for (var i = 0; i < labels.length; i++) {
16 | var label = labels[i];
17 |
18 | if (label.LanguageCode == change) {
19 | label.Label = changes[change];
20 | label.HasChanged = true;
21 |
22 | break;
23 | }
24 |
25 | // Did not find label for this language
26 | if (i === labels.length - 1) {
27 | labels.push({ LanguageCode: change, Label: changes[change] })
28 | }
29 | }
30 | }
31 | }
32 |
33 | function GetUpdates() {
34 | var records = XrmTranslator.GetGrid().records;
35 |
36 | var updates = [];
37 |
38 | for (var i = 0; i < records.length; i++) {
39 | var record = records[i];
40 |
41 | if (record.w2ui && record.w2ui.changes) {
42 | var chart = XrmTranslator.GetAttributeByProperty("recid", record.recid);
43 | var labels = chart.labels.Label.LocalizedLabels;
44 |
45 | var changes = record.w2ui.changes;
46 |
47 | ApplyChanges(changes, labels);
48 | updates.push(chart);
49 | }
50 | }
51 |
52 | return updates;
53 | }
54 | function FillTable() {
55 | var grid = XrmTranslator.GetGrid();
56 | grid.clear();
57 |
58 | var records = [];
59 |
60 | for (var i = 0; i < XrmTranslator.metadata.length; i++) {
61 | var chart = XrmTranslator.metadata[i];
62 |
63 | var displayNames = chart.labels.Label.LocalizedLabels;
64 |
65 | if (!displayNames || displayNames.length === 0) {
66 | continue;
67 | }
68 |
69 | var record = {
70 | recid: chart.recid,
71 | schemaName: "Chart"
72 | };
73 |
74 | for (var j = 0; j < displayNames.length; j++) {
75 | var displayName = displayNames[j];
76 |
77 | record[displayName.LanguageCode.toString()] = displayName.Label;
78 | }
79 |
80 | records.push(record);
81 | }
82 |
83 | XrmTranslator.AddSummary(records);
84 | grid.add(records);
85 | grid.unlock();
86 | }
87 |
88 | ChartHandler.Load = function () {
89 | var entityName = XrmTranslator.GetEntity();
90 |
91 | var entityMetadataId = XrmTranslator.entityMetadata[entityName];
92 |
93 | var queryRequest = {
94 | entityName: "savedqueryvisualization",
95 | queryParams: "?$filter=primaryentitytypecode eq '" + entityName.toLowerCase() + "' and iscustomizable/Value eq true&$orderby=savedqueryvisualizationid asc"
96 | };
97 |
98 | var languages = XrmTranslator.installedLanguages.LocaleIds;
99 | var initialLanguage = XrmTranslator.userSettings.uilanguageid;
100 |
101 | return WebApiClient.Retrieve(queryRequest)
102 | .then(function (response) {
103 |
104 | var charts = response.value;
105 | var requests = [];
106 |
107 | for (var i = 0; i < charts.length; i++) {
108 | var chart = charts[i];
109 |
110 | var retrieveLabelsRequest = WebApiClient.Requests.RetrieveLocLabelsRequest
111 | .with({
112 | urlParams: {
113 | EntityMoniker: "{'@odata.id':'savedqueryvisualizations(" + chart.savedqueryvisualizationid + ")'}",
114 | AttributeName: "'name'",
115 | IncludeUnpublished: true
116 | }
117 | })
118 |
119 | var prop = WebApiClient.Promise.props({
120 | recid: chart.savedqueryvisualizationid,
121 | labels: WebApiClient.Execute(retrieveLabelsRequest)
122 | });
123 |
124 | requests.push(prop);
125 | }
126 |
127 | return WebApiClient.Promise.all(requests);
128 | })
129 | .then(function (responses) {
130 | var charts = responses;
131 | XrmTranslator.metadata = charts;
132 |
133 | FillTable();
134 | })
135 | .catch(XrmTranslator.errorHandler);
136 | }
137 |
138 | ChartHandler.Save = function () {
139 | XrmTranslator.LockGrid("Saving");
140 |
141 | var updates = GetUpdates();
142 | var requests = [];
143 |
144 | for (var i = 0; i < updates.length; i++) {
145 | var update = updates[i];
146 |
147 | var request = WebApiClient.Requests.SetLocLabelsRequest
148 | .with({
149 | payload: {
150 | Labels: update.labels.Label.LocalizedLabels,
151 | EntityMoniker: {
152 | "@odata.type": "Microsoft.Dynamics.CRM.savedqueryvisualization",
153 | savedqueryvisualizationid: update.recid
154 | },
155 | AttributeName: "name"
156 | }
157 | });
158 |
159 | requests.push(request);
160 | }
161 |
162 | return WebApiClient.Promise.resolve(requests)
163 | .each(function (request) {
164 | return WebApiClient.Execute(request);
165 | })
166 | .then(function (response) {
167 | XrmTranslator.LockGrid("Publishing");
168 |
169 | return XrmTranslator.Publish();
170 | })
171 | .then(function(response) {
172 | return XrmTranslator.AddToSolution(updates.map(function(u) { return u.recid; }), XrmTranslator.ComponentType.SavedQueryVisualization);
173 | })
174 | .then(function(response) {
175 | return XrmTranslator.ReleaseLockAndPrompt();
176 | })
177 | .then(function (response) {
178 | XrmTranslator.LockGrid("Reloading");
179 |
180 | return ChartHandler.Load();
181 | })
182 | .catch(XrmTranslator.errorHandler);
183 | }
184 |
185 | }(window.ChartHandler = window.ChartHandler || {}));
186 |
--------------------------------------------------------------------------------
/src/js/Translator/ViewHandler.js:
--------------------------------------------------------------------------------
1 | /* @preserve
2 | * MIT License
3 | *
4 | * Copyright (c) 2017 Florian Krönert
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | */
25 | (function (ViewHandler, undefined) {
26 | "use strict";
27 |
28 | function ApplyChanges(changes, labels) {
29 | for (var change in changes) {
30 | if (!changes.hasOwnProperty(change)) {
31 | continue;
32 | }
33 |
34 | // Skip empty labels
35 | if (!changes[change]) {
36 | continue;
37 | }
38 |
39 | for (var i = 0; i < labels.length; i++) {
40 | var label = labels[i];
41 |
42 | if (label.LanguageCode == change) {
43 | label.Label = changes[change];
44 | label.HasChanged = true;
45 |
46 | break;
47 | }
48 |
49 | // Did not find label for this language
50 | if (i === labels.length - 1) {
51 | labels.push({ LanguageCode: change, Label: changes[change] })
52 | }
53 | }
54 | }
55 | }
56 |
57 | function GetUpdates() {
58 | var records = XrmTranslator.GetGrid().records;
59 |
60 | var updates = [];
61 |
62 | for (var i = 0; i < records.length; i++) {
63 | var record = records[i];
64 |
65 | if (record.w2ui && record.w2ui.changes) {
66 | var view = XrmTranslator.GetAttributeByProperty("recid", record.recid);
67 | var labels = view.labels.Label.LocalizedLabels;
68 |
69 | var changes = record.w2ui.changes;
70 |
71 | ApplyChanges(changes, labels);
72 | updates.push(view);
73 | }
74 | }
75 |
76 | return updates;
77 | }
78 |
79 | function FillTable () {
80 | var grid = XrmTranslator.GetGrid();
81 | grid.clear();
82 |
83 | var records = [];
84 |
85 | for (var i = 0; i < XrmTranslator.metadata.length; i++) {
86 | var view = XrmTranslator.metadata[i];
87 |
88 | var displayNames = view.labels.Label.LocalizedLabels;
89 |
90 | if (!displayNames || displayNames.length === 0) {
91 | continue;
92 | }
93 |
94 | var record = {
95 | recid: view.recid,
96 | schemaName: "View"
97 | };
98 |
99 | for (var j = 0; j < displayNames.length; j++) {
100 | var displayName = displayNames[j];
101 |
102 | record[displayName.LanguageCode.toString()] = displayName.Label;
103 | }
104 |
105 | records.push(record);
106 | }
107 |
108 | XrmTranslator.AddSummary(records);
109 | grid.add(records);
110 | grid.unlock();
111 | }
112 |
113 | ViewHandler.Load = function() {
114 | var entityName = XrmTranslator.GetEntity();
115 |
116 | var entityMetadataId = XrmTranslator.entityMetadata[entityName];
117 |
118 | var queryRequest = {
119 | entityName: "savedquery",
120 | queryParams: "?$filter=returnedtypecode eq '" + entityName.toLowerCase() + "' and iscustomizable/Value eq true&$orderby=savedqueryid asc"
121 | };
122 |
123 | var languages = XrmTranslator.installedLanguages.LocaleIds;
124 | var initialLanguage = XrmTranslator.userSettings.uilanguageid;
125 |
126 | return WebApiClient.Retrieve(queryRequest)
127 | .then(function(response) {
128 | var views = response.value;
129 | var requests = [];
130 |
131 | for (var i = 0; i < views.length; i++) {
132 | var view = views[i];
133 |
134 | var retrieveLabelsRequest = WebApiClient.Requests.RetrieveLocLabelsRequest
135 | .with({
136 | urlParams: {
137 | EntityMoniker: "{'@odata.id':'savedqueries(" + view.savedqueryid + ")'}",
138 | AttributeName: "'name'",
139 | IncludeUnpublished: true
140 | }
141 | })
142 |
143 | var prop = WebApiClient.Promise.props({
144 | recid: view.savedqueryid,
145 | labels: WebApiClient.Execute(retrieveLabelsRequest)
146 | });
147 |
148 | requests.push(prop);
149 | }
150 |
151 | return WebApiClient.Promise.all(requests);
152 | })
153 | .then(function(responses) {
154 | var views = responses;
155 | XrmTranslator.metadata = views;
156 |
157 | FillTable();
158 | })
159 | .catch(XrmTranslator.errorHandler);
160 | }
161 |
162 | ViewHandler.Save = function() {
163 | XrmTranslator.LockGrid("Saving");
164 |
165 | var updates = GetUpdates();
166 | var requests = [];
167 |
168 | for (var i = 0; i < updates.length; i++) {
169 | var update = updates[i];
170 |
171 | var request = WebApiClient.Requests.SetLocLabelsRequest
172 | .with({
173 | payload: {
174 | Labels: update.labels.Label.LocalizedLabels,
175 | EntityMoniker: {
176 | "@odata.type": "Microsoft.Dynamics.CRM.savedquery",
177 | savedqueryid: update.recid
178 | },
179 | AttributeName: "name"
180 | }
181 | });
182 |
183 | requests.push(request);
184 | }
185 |
186 | return WebApiClient.Promise.resolve(requests)
187 | .each(function(request) {
188 | return WebApiClient.Execute(request);
189 | })
190 | .then(function (response){
191 | XrmTranslator.LockGrid("Publishing");
192 |
193 | return XrmTranslator.Publish();
194 | })
195 | .then(function(response) {
196 | return XrmTranslator.AddToSolution(updates.map(function(u) { return u.recid; }), XrmTranslator.ComponentType.SavedQuery);
197 | })
198 | .then(function(response) {
199 | return XrmTranslator.ReleaseLockAndPrompt();
200 | })
201 | .then(function (response) {
202 | XrmTranslator.LockGrid("Reloading");
203 |
204 | return ViewHandler.Load();
205 | })
206 | .catch(XrmTranslator.errorHandler);
207 | }
208 | } (window.ViewHandler = window.ViewHandler || {}));
209 |
--------------------------------------------------------------------------------
/src/js/Translator/AttributeHandler.js:
--------------------------------------------------------------------------------
1 | /* @preserve
2 | * MIT License
3 | *
4 | * Copyright (c) 2017 Florian Krönert
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | */
25 | (function (AttributeHandler, undefined) {
26 | "use strict";
27 |
28 | function ApplyChanges(changes, labels) {
29 | for (var change in changes) {
30 | if (!changes.hasOwnProperty(change)) {
31 | continue;
32 | }
33 |
34 | // Skip empty labels
35 | if (!changes[change]) {
36 | continue;
37 | }
38 |
39 | for (var i = 0; i < labels.length; i++) {
40 | var label = labels[i];
41 |
42 | if (label.LanguageCode == change) {
43 | label.Label = changes[change];
44 | label.HasChanged = true;
45 |
46 | break;
47 | }
48 |
49 | // Did not find label for this language
50 | if (i === labels.length - 1) {
51 | labels.push({ LanguageCode: change, Label: changes[change] })
52 | }
53 | }
54 | }
55 | }
56 |
57 | function GetUpdates() {
58 | var records = XrmTranslator.GetGrid().records;
59 |
60 | var updates = [];
61 |
62 | for (var i = 0; i < records.length; i++) {
63 | var record = records[i];
64 |
65 | if (record.w2ui && record.w2ui.changes) {
66 | var attribute = XrmTranslator.GetAttributeById (record.recid);
67 | var labels = attribute[XrmTranslator.GetComponent()].LocalizedLabels;
68 |
69 | var changes = record.w2ui.changes;
70 |
71 | ApplyChanges(changes, labels);
72 | updates.push(attribute);
73 | }
74 | }
75 |
76 | return updates;
77 | }
78 |
79 | function FillTable () {
80 | var grid = XrmTranslator.GetGrid();
81 | grid.clear();
82 |
83 | var records = [];
84 |
85 | var excludedColumns = XrmTranslator.metadata.reduce(function(all, attribute) {
86 | // If attribute has a formula definition, it is a rollup field.
87 | // Their accompanying fields for date, state and base cause CRM exceptions when being translated, so we need to skip these
88 | if (attribute.FormulaDefinition) {
89 | if (attribute.AttributeType === "Money") {
90 | /// Skip _Base, _Date, _State
91 | all.push(attribute.SchemaName + "_Base", attribute.SchemaName + "_Date", attribute.SchemaName + "_State");
92 | }
93 | else {
94 | // Skip _Date, _State
95 | all.push(attribute.SchemaName + "_Date", attribute.SchemaName + "_State");
96 | }
97 | }
98 | // Some attributes such as versionnumber can not be renamed / translated
99 | else if (attribute.IsRenameable && !attribute.IsRenameable.Value) {
100 | all.push(attribute.SchemaName);
101 | }
102 |
103 | return all;
104 | }, []);
105 |
106 | for (var i = 0; i < XrmTranslator.metadata.length; i++) {
107 | var attribute = XrmTranslator.metadata[i];
108 |
109 | if (excludedColumns.indexOf(attribute.SchemaName) !== -1) {
110 | continue;
111 | }
112 |
113 | var displayNames = attribute[XrmTranslator.GetComponent()].LocalizedLabels;
114 |
115 | if (!displayNames || displayNames.length === 0) {
116 | continue;
117 | }
118 |
119 | var record = {
120 | recid: attribute.MetadataId,
121 | schemaName: attribute.SchemaName
122 | };
123 |
124 | for (var j = 0; j < displayNames.length; j++) {
125 | var displayName = displayNames[j];
126 |
127 | record[displayName.LanguageCode.toString()] = displayName.Label;
128 | }
129 |
130 | records.push(record);
131 | }
132 |
133 | XrmTranslator.AddSummary(records);
134 | grid.add(records);
135 | grid.unlock();
136 | }
137 |
138 | AttributeHandler.Load = function() {
139 | var entityName = XrmTranslator.GetEntity();
140 |
141 | var entityMetadataId = XrmTranslator.entityMetadata[entityName];
142 |
143 | var request = {
144 | entityName: "EntityDefinition",
145 | entityId: entityMetadataId,
146 | queryParams: "/Attributes?$filter=IsCustomizable/Value eq true"
147 | };
148 |
149 | return WebApiClient.Retrieve(request)
150 | .then(function(response) {
151 | var attributes = response.value.sort(XrmTranslator.SchemaNameComparer);
152 | XrmTranslator.metadata = attributes;
153 |
154 | FillTable();
155 | })
156 | .catch(XrmTranslator.errorHandler);
157 | }
158 |
159 | AttributeHandler.Save = function() {
160 | XrmTranslator.LockGrid("Saving");
161 |
162 | var updates = GetUpdates();
163 |
164 | var requests = [];
165 | var entityUrl = WebApiClient.GetApiUrl() + "EntityDefinitions(" + XrmTranslator.GetEntityId() + ")/Attributes(";
166 |
167 | for (var i = 0; i < updates.length; i++) {
168 | var update = updates[i];
169 | var url = entityUrl + update.MetadataId + ")";
170 |
171 | var request = {
172 | method: "PUT",
173 | url: url,
174 | attribute: update,
175 | headers: [{key: "MSCRM.MergeLabels", value: "true"}]
176 | };
177 | requests.push(request);
178 | }
179 |
180 | return WebApiClient.Promise.resolve(requests)
181 | .each(function(request) {
182 | return WebApiClient.SendRequest(request.method, request.url, request.attribute, request.headers);
183 | })
184 | .then(function (response){
185 | XrmTranslator.LockGrid("Publishing");
186 |
187 | return XrmTranslator.Publish();
188 | })
189 | .then(function(response) {
190 | return XrmTranslator.AddToSolution(updates.map(function(u) { return u.MetadataId; }), XrmTranslator.ComponentType.Attribute);
191 | })
192 | .then(function(response) {
193 | return XrmTranslator.ReleaseLockAndPrompt();
194 | })
195 | .then(function (response) {
196 | XrmTranslator.LockGrid("Reloading");
197 |
198 | return AttributeHandler.Load();
199 | })
200 | .catch(XrmTranslator.errorHandler);
201 | }
202 | } (window.AttributeHandler = window.AttributeHandler || {}));
203 |
--------------------------------------------------------------------------------
/src/js/PropertyEditor/AttributePropertyHandler.js:
--------------------------------------------------------------------------------
1 | /* @preserve
2 | * MIT License
3 | *
4 | * Copyright (c) 2017 Florian Krönert
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | */
25 | (function (AttributePropertyHandler, undefined) {
26 | "use strict";
27 |
28 | var levels = [{ id: "None", text: "None" },
29 | { id: "Recommended", text: "Recommended" },
30 | { id: "ApplicationRequired", text: "Application Required" },
31 | { id: "SystemRequired", text: "System Required" }];
32 |
33 | function ApplyChanges(attribute, changes) {
34 | for (var change in changes) {
35 | if (!changes.hasOwnProperty(change)) {
36 | continue;
37 | }
38 |
39 | if (change === "RequiredLevel") {
40 | // System required can not be changed or set
41 | if (attribute.RequiredLevel.Value === "SystemRequired" || changes[change] === "SystemRequired") {
42 | continue;
43 | }
44 | }
45 |
46 | if (attribute[change].ManagedPropertyLogicalName) {
47 | attribute[change].Value = changes[change];
48 | } else {
49 | attribute[change] = changes[change];
50 | }
51 | }
52 | }
53 |
54 | function GetUpdates() {
55 | var records = XrmPropertyEditor.GetGrid().records;
56 |
57 | var updates = [];
58 |
59 | for (var i = 0; i < records.length; i++) {
60 | var record = records[i];
61 |
62 | if (record.w2ui && record.w2ui.changes) {
63 | var attribute = XrmPropertyEditor.GetAttributeById (record.recid);
64 |
65 | var changes = record.w2ui.changes;
66 |
67 | ApplyChanges(attribute, changes);
68 | updates.push(attribute);
69 | }
70 | }
71 |
72 | return updates;
73 | }
74 |
75 | function FillTable () {
76 | var grid = XrmPropertyEditor.GetGrid();
77 | grid.clear();
78 |
79 | var records = [];
80 |
81 | for (var i = 0; i < XrmPropertyEditor.metadata.length; i++) {
82 | var attribute = XrmPropertyEditor.metadata[i];
83 |
84 | var record = {
85 | recid: attribute.MetadataId,
86 | schemaName: attribute.SchemaName,
87 | RequiredLevel: attribute.RequiredLevel.Value,
88 | IsAuditEnabled: attribute.IsAuditEnabled.Value,
89 | IsValidForAdvancedFind: attribute.IsValidForAdvancedFind.Value,
90 | IsSecured: attribute.IsSecured
91 | };
92 |
93 | records.push(record);
94 | }
95 |
96 | grid.add(records);
97 | grid.unlock();
98 | }
99 |
100 | function InitializeColumns () {
101 | var grid = XrmPropertyEditor.GetGrid();
102 |
103 | var columnSize = 100 / 5;
104 |
105 | grid.columns = [
106 | { field: 'schemaName', caption: 'Schema Name', size: columnSize + '%', sortable: true, resizable: true, frozen: true },
107 | { field: 'RequiredLevel', caption: 'Required Level', size: columnSize + '%', sortable: true, resizable: true,
108 | editable: { type: 'select', items: levels, showAll: true },
109 | render: function (record, index, col_index) {
110 | var html = '';
111 | for (var i = 0; i < levels.length; i++) {
112 | var level = levels[i]
113 | if (level.id == this.getCellValue(index, col_index)) {
114 | html = level.text;
115 | }
116 | }
117 | return html;
118 | }
119 | },
120 | { field: 'IsAuditEnabled', caption: 'Is Audit Enabled', size: columnSize + '%', sortable: true, resizable: true, style: 'text-align: center',
121 | editable: { type: 'checkbox', style: 'text-align: center' }
122 | },
123 | { field: 'IsValidForAdvancedFind', caption: 'Is Valid For Advanced Find', size: columnSize + '%', sortable: true, resizable: true, style: 'text-align: center',
124 | editable: { type: 'checkbox', style: 'text-align: center' }
125 | },
126 | { field: 'IsSecured', caption: 'Is Secured', size: columnSize + '%', sortable: true, resizable: true, style: 'text-align: center',
127 | editable: { type: 'checkbox', style: 'text-align: center' }
128 | }
129 | ];
130 |
131 | grid.refresh();
132 | }
133 |
134 | AttributePropertyHandler.Load = function() {
135 | XrmPropertyEditor.RestoreInitialColumns();
136 | InitializeColumns();
137 | var entityName = XrmPropertyEditor.GetEntity();
138 | var entityMetadataId = XrmPropertyEditor.entityMetadata[entityName];
139 |
140 | var request = {
141 | entityName: "EntityDefinition",
142 | entityId: entityMetadataId,
143 | queryParams: "/Attributes?$filter=IsCustomizable/Value eq true"
144 | };
145 |
146 | WebApiClient.Retrieve(request)
147 | .then(function(response) {
148 | var attributes = response.value.sort(XrmPropertyEditor.SchemaNameComparer);
149 | XrmPropertyEditor.metadata = attributes;
150 |
151 | FillTable();
152 | })
153 | .catch(XrmPropertyEditor.errorHandler);
154 | }
155 |
156 | AttributePropertyHandler.Save = function() {
157 | XrmPropertyEditor.LockGrid("Saving");
158 |
159 | var updates = GetUpdates();
160 |
161 | var requests = [];
162 | var entityUrl = WebApiClient.GetApiUrl() + "EntityDefinitions(" + XrmPropertyEditor.GetEntityId() + ")/Attributes(";
163 |
164 | for (var i = 0; i < updates.length; i++) {
165 | var update = updates[i];
166 | var url = entityUrl + update.MetadataId + ")";
167 |
168 | var request = {
169 | method: "PUT",
170 | url: url,
171 | attribute: update,
172 | headers: [{key: "MSCRM.MergeLabels", value: "true"}]
173 | };
174 | requests.push(request);
175 | }
176 |
177 | WebApiClient.Promise.resolve(requests)
178 | .each(function(request) {
179 | return WebApiClient.SendRequest(request.method, request.url, request.attribute, request.headers);
180 | })
181 | .then(function (response){
182 | XrmPropertyEditor.LockGrid("Publishing");
183 |
184 | return XrmPropertyEditor.Publish();
185 | })
186 | .then(function (response) {
187 | XrmPropertyEditor.LockGrid("Reloading");
188 |
189 | return AttributePropertyHandler.Load();
190 | })
191 | .catch(XrmPropertyEditor.errorHandler);
192 | }
193 | } (window.AttributePropertyHandler = window.AttributePropertyHandler || {}));
194 |
--------------------------------------------------------------------------------
/src/js/Translator/FormMetaHandler.js:
--------------------------------------------------------------------------------
1 | /* @preserve
2 | * MIT License
3 | *
4 | * Copyright (c) 2017 Florian Krönert
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | */
25 | (function (FormMetaHandler, undefined) {
26 | "use strict";
27 |
28 | function ApplyChanges(changes, labels) {
29 | for (var change in changes) {
30 | if (!changes.hasOwnProperty(change)) {
31 | continue;
32 | }
33 |
34 | // Skip empty labels
35 | if (!changes[change]) {
36 | continue;
37 | }
38 |
39 | for (var i = 0; i < labels.length; i++) {
40 | var label = labels[i];
41 |
42 | if (label.LanguageCode == change) {
43 | label.Label = changes[change];
44 | label.HasChanged = true;
45 |
46 | break;
47 | }
48 |
49 | // Did not find label for this language
50 | if (i === labels.length - 1) {
51 | labels.push({ LanguageCode: change, Label: changes[change] })
52 | }
53 | }
54 | }
55 | }
56 |
57 | function GetUpdates() {
58 | var records = XrmTranslator.GetGrid().records;
59 |
60 | var updates = [];
61 |
62 | for (var i = 0; i < records.length; i++) {
63 | var record = records[i];
64 |
65 | if (record.w2ui && record.w2ui.changes) {
66 | var view = XrmTranslator.GetAttributeByProperty("recid", record.recid);
67 | var labels = view.labels.Label.LocalizedLabels;
68 |
69 | var changes = record.w2ui.changes;
70 |
71 | ApplyChanges(changes, labels);
72 | updates.push(view);
73 | }
74 | }
75 |
76 | return updates;
77 | }
78 |
79 | function FillTable () {
80 | var grid = XrmTranslator.GetGrid();
81 | grid.clear();
82 |
83 | var records = [];
84 |
85 | for (var i = 0; i < XrmTranslator.metadata.length; i++) {
86 | var form = XrmTranslator.metadata[i];
87 |
88 | var displayNames = form.labels.Label.LocalizedLabels;
89 |
90 | if (!displayNames || displayNames.length === 0) {
91 | continue;
92 | }
93 |
94 | var record = {
95 | recid: form.recid,
96 | schemaName: "Form"
97 | };
98 |
99 | for (var j = 0; j < displayNames.length; j++) {
100 | var displayName = displayNames[j];
101 |
102 | record[displayName.LanguageCode.toString()] = displayName.Label;
103 | }
104 |
105 | records.push(record);
106 | }
107 |
108 | XrmTranslator.AddSummary(records);
109 | grid.add(records);
110 | grid.unlock();
111 | }
112 |
113 | FormMetaHandler.Load = function() {
114 | var entityName = XrmTranslator.GetEntity();
115 |
116 | var entityMetadataId = XrmTranslator.entityMetadata[entityName];
117 |
118 | var formRequest = {
119 | entityName: "systemform",
120 | queryParams: "?$filter=objecttypecode eq '" + entityName.toLowerCase() + "' and iscustomizable/Value eq true and formactivationstate eq 1"
121 | };
122 |
123 | if (entityName.toLowerCase() === "none") {
124 | formRequest.queryParams = "?$filter=formactivationstate eq 1 and iscustomizable/Value eq true and (type eq 0 or type eq 10)"
125 | }
126 |
127 | return WebApiClient.Retrieve(formRequest)
128 | .then(function(response) {
129 | var forms = response.value;
130 | var requests = [];
131 |
132 | for (var i = 0; i < forms.length; i++) {
133 | var form = forms[i];
134 |
135 | var retrieveLabelsRequest = WebApiClient.Requests.RetrieveLocLabelsRequest
136 | .with({
137 | urlParams: {
138 | EntityMoniker: "{'@odata.id':'systemforms(" + form.formid + ")'}",
139 | AttributeName: "'name'",
140 | IncludeUnpublished: true
141 | }
142 | })
143 |
144 | var prop = WebApiClient.Promise.props({
145 | recid: form.formid,
146 | labels: WebApiClient.Execute(retrieveLabelsRequest)
147 | });
148 |
149 | requests.push(prop);
150 | }
151 |
152 | return WebApiClient.Promise.all(requests);
153 | })
154 | .then(function(responses) {
155 | var forms = responses;
156 | XrmTranslator.metadata = forms;
157 |
158 | FillTable();
159 | })
160 | .catch(XrmTranslator.errorHandler);
161 | }
162 |
163 | FormMetaHandler.Save = function() {
164 | XrmTranslator.LockGrid("Saving");
165 |
166 | var updates = GetUpdates();
167 | var requests = [];
168 |
169 | for (var i = 0; i < updates.length; i++) {
170 | var update = updates[i];
171 |
172 | var request = WebApiClient.Requests.SetLocLabelsRequest
173 | .with({
174 | payload: {
175 | Labels: update.labels.Label.LocalizedLabels,
176 | EntityMoniker: {
177 | "@odata.type": "Microsoft.Dynamics.CRM.systemform",
178 | formid: update.recid
179 | },
180 | AttributeName: "name"
181 | }
182 | });
183 |
184 | requests.push(request);
185 | }
186 |
187 | return WebApiClient.Promise.resolve(requests)
188 | .each(function(request) {
189 | return WebApiClient.Execute(request);
190 | })
191 | .then(function (response){
192 | XrmTranslator.LockGrid("Publishing");
193 | var entityName = XrmTranslator.GetEntity();
194 | if (entityName.toLowerCase() === "none") {
195 | return XrmTranslator.PublishDashboard(updates);
196 | }
197 | else {
198 | return XrmTranslator.Publish();
199 | }
200 | })
201 | .then(function(response) {
202 | if (XrmTranslator.GetEntity().toLowerCase() === "none") {
203 | return XrmTranslator.AddToSolution(updates.map(function(u) { return u.recid; }), XrmTranslator.ComponentType.SystemForm, true, true);
204 | }
205 | else {
206 | return XrmTranslator.AddToSolution(updates.map(function(u) { return u.recid; }), XrmTranslator.ComponentType.SystemForm);
207 | }
208 | })
209 | .then(function(response) {
210 | return XrmTranslator.ReleaseLockAndPrompt();
211 | })
212 | .then(function (response) {
213 | XrmTranslator.LockGrid("Reloading");
214 |
215 | return FormMetaHandler.Load();
216 | })
217 | .catch(XrmTranslator.errorHandler);
218 | }
219 | } (window.FormMetaHandler = window.FormMetaHandler || {}));
220 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Dynamics CRM Quick Edit
2 |
3 | ## Purpose
4 | This is a tool that eases development tasks in CRM.
5 | It supports changing of existing translations and adding of new translations for all kind of CRM parts.
6 | In addition to that, you can change properties such as field security status on fields in bulk.
7 |
8 | There is an automated translation feature for missing labels, that tries to get a translation using the free Glosbe translation API.
9 |
10 | This is a beta, use at your own risk and export a backup solution before testing.
11 |
12 | ## How to use
13 | After installing the solution (download latest version [here](https://github.com/DigitalFlow/Xrm-Quick-Edit/releases)), there will be some dashboards and their requirements added to your organization.
14 |
15 | ## Translating Dynamics 365 Portals
16 | Since v3.7.0, content snippets for Dynamics 365 Portals can be translated as well.
17 | For doing so, simply choose "Content Snippet (Adx_contentsnippet)" as entity to translate and "Content" as type.
18 | You will then be able to translate the values of content snippets.
19 | Be aware that all languages of all websites are added as columns.
20 | If any values are not saved, you most probably don't have that specific language enabled for the contained website.
21 |
22 | ## Configuration
23 | There are multiple settings which you can manipulate inside the "oss_/XrmQuickEdit/config/XrmQuickEditConfig.js" webresource.
24 |
25 | ### entityWhiteList
26 | Type: Array
27 | List of entity logical names which should be available in the translation dashboard. Allows all if empty.
28 |
29 | ### hideAutoTranslate
30 | Type: boolean
31 | Define whether to hide the Auto Translate button.
32 |
33 | ### hideFindAndReplace
34 | Type: boolean
35 | Define whether to hide the Find and Replace button.
36 |
37 | ### hideLanguagesByDefault
38 | Type: boolean
39 | Define whether to hide all language columns but the current user's language by default. More columns can then be included in the grid.
40 |
41 | ### lockedLanguages
42 | Type: Array
43 | Locale IDs of Languages that should not be translatable
44 |
45 | ### solutionUniqueName
46 | Type: string
47 | If set, components that were translated will be automatically added to the solution with the defined unique name.
48 |
49 | ## Dashboards
50 | ### Translation Management Dashboard
51 | There will be a column in the translation grid for every language installed in the organization.
52 | Once the list of entities is loaded, select the one you want to translate, as well as which part.
53 | For entities and attributes you can even select, whether you want to translate the display names, or the descriptions.
54 | This does not have an effect on any of the other types right now.
55 | Just add/change the translations using inline-editing in the grid.
56 | For missing translations, you can click the Auto Translate button, which will try to find fitting translations and enter them for you. You'll first have to select the source LCID, which is the column name of the column that contains the labels that should be translated and the destination LCID, which is the column name of the column that should be translated automatically.
57 |
58 | After you did your changes, the save button will be enabled. By clicking it, the labels will be saved to CRM and the entity will be published.
59 |
60 | #### Attributes
61 | 
62 |
63 | #### OptionSet Values
64 | 
65 |
66 | #### Views
67 | 
68 |
69 | #### System Forms
70 | 
71 |
72 | Note regarding form translations: Unfortunately the CRM only returns the current user's language labels when retrieving a system form. Other language labels, even if present, are not returned. Therefore the dashboard changes the user language to each installed language and retrieves the form, for being able to display all labels. After having retrieved all of the forms, your user language is restored to your initial value again.
73 | So please note that you should not abort loading of a form, as you might end up with a different language id (which you can of course just switch back in your options).
74 | In addition to that, sometimes publishing of CRM forms does not finish, if the UI language does not match the base language. Be sure to upgrade to at least v2.6.1 of this project, because since this version, the UI language is set to the base language before saving and publishing the changes. Your initial language is restored afterwards.
75 | If you still experience issues with the latest version, please file an issue on GitHub. When publishing should get stuck, publish changes on another entity and try again afterwards.
76 |
77 | ##### Form Labels are not updated
78 | You might come across issues where you translate attributes and the labels in the form do not update appropriately.
79 | In that case, you probably overwrote the form labels for this attribute, which is why changes in the attribute label take no effect.
80 | Since v3.15.0 there is a cure for this issue: When inside the form translator, there is a button "Remove Overridden Attribute Labels", which removes all overridden attribute labels from your form. After that, your update attribute labels should display in the form as well again.
81 |
82 | > Use "Remove Overridden Attribute Labels" at your own risk. Please backup the forms before using this function by putting them in a solution and exporting it.
83 |
84 | #### System Form Names
85 | 
86 |
87 | #### Entity Display (Collection) Names
88 | 
89 |
90 | #### Functions
91 | ##### Find and Replace
92 | When clicking Find and Replace, you can enter your search text as either regex (JS style) or plain text.
93 | There is an option for ignoring the case when searching for matches.
94 | When using it with regular expressions, JS regular expressions are used. This gives you also the possibility for using capture groups and reordering in your replace expression. For example when using find text: ```(Account) (.*)``` and replace text: ```$2 $1``` you can reorder the text, so that `Account Number` becomes `Number Account`.
95 |
96 | 
97 |
98 | After the find and replace has processed all records, you will be presented with a selection dialog.
99 | Select all replacements that you want to apply and they will be changed in the grid.
100 |
101 | 
102 |
103 | ### Property Editor Dashboard
104 | 
105 |
106 | This is a dashboard for changing properties on CRM parts. Currently there is support for CRM fields only.
107 | You can change the following properties in bulk:
108 | - Required Level
109 | - Is Audit Enabled
110 | - Is Valid for Advanced Find
111 | - Is Secured
112 |
113 | Afterwards hit "Save" and the updates will be sent and published.
114 | When changing the field security state on fields, you might receive the following error:
115 | ```
116 | The user does not have full permissions to unsecure the attribute...
117 | ```
118 |
119 | There seems to be a background workflow in CRM that works on change of these properties, if you receive above error, wait a few minutes and try again.
120 | Setting required level of a field to SystemRequired as well as trying to change a field's level from SystemRequired will not send any updates for this, since this is not allowed.
121 |
122 |
123 | ## System requirements
124 | ### CRM Version
125 | This solution is available for CRM 2016 >= 8.0, since it requires the Web API for operating.
126 |
127 | ### User permissions
128 | This tool uses a wide range of metadata operations, your user should best be system administrator.
129 |
130 | ## Tools used
131 | I used [jQuery](https://github.com/jquery/jquery) and [w2ui](https://github.com/vitmalina/w2ui) for working with the grid.
132 | Requests to the CRM are sent using my [Web API Client](https://github.com/DigitalFlow/Xrm-WebApi-Client).
133 | Automated translations are gathered using the awesome [Glosbe translation API](https://de.glosbe.com/a-api).
134 |
135 | ## License
136 | This tool is licensed under the MIT, enjoy!
137 |
--------------------------------------------------------------------------------
/src/js/Translator/ContentSnippetHandler.js:
--------------------------------------------------------------------------------
1 | /* @preserve
2 | * MIT License
3 | *
4 | * Copyright (c) 2017 Florian Krönert
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | */
25 | (function (ContentSnippetHandler, undefined) {
26 | "use strict";
27 | let snippets = [];
28 | let idSeparator = "|";
29 | let websites = [];
30 | let portalLanguages = [];
31 |
32 | function GetWebsiteId (id) {
33 | var separatorIndex = id.indexOf(idSeparator);
34 |
35 | if (separatorIndex === -1) {
36 | return id;
37 | }
38 |
39 | return id.substring(0, separatorIndex);
40 | }
41 |
42 | function GetPayload (contentSnippet, websiteId, languageId, value, snippetName) {
43 | if (!websiteId || !languageId) {
44 | return undefined;
45 | }
46 |
47 | return {
48 | entityName: "adx_contentsnippet",
49 | entityId: contentSnippet ? contentSnippet.adx_contentsnippetid : undefined,
50 | entity: {
51 | adx_name: snippetName,
52 | adx_value: w2utils.decodeTags(value),
53 | "adx_websiteid@odata.bind": "/adx_websites(" + websiteId + ")",
54 | "adx_contentsnippetlanguageid@odata.bind": "/adx_websitelanguages(" + languageId + ")"
55 | }
56 | };
57 | }
58 |
59 | function GetUpdates(records) {
60 | var updates = [];
61 |
62 | var languageList = portalLanguages;
63 |
64 | for (var i = 0; i < records.length; i++) {
65 | var record = records[i];
66 |
67 | if (record.w2ui && record.w2ui.changes) {
68 | var websiteId = GetWebsiteId(record.recid);
69 | var snippetName = record.recid.replace(websiteId + idSeparator, "");
70 |
71 | var changes = record.w2ui.changes;
72 |
73 | for (var change in changes) {
74 | if (!changes.hasOwnProperty(change)) {
75 | continue;
76 | }
77 |
78 | // Skip empty data
79 | if (!changes[change]) {
80 | continue;
81 | }
82 |
83 | var language = languageList.find(function(l) { return l._adx_websiteid_value === websiteId && l.adx_PortalLanguageId.adx_lcid == change });
84 |
85 | // This will be the case when the language has not been enabled for the website this content snippet belongs to
86 | if (!language) {
87 | continue;
88 | }
89 |
90 | var snippet = snippets.find(function(s) { return s.adx_name === snippetName && s.adx_contentsnippetlanguageid && s.adx_contentsnippetlanguageid.adx_websitelanguageid === language.adx_websitelanguageid });
91 | var update = GetPayload(snippet, websiteId, language.adx_websitelanguageid, changes[change], snippetName);
92 |
93 | if (update) {
94 | updates.push(update);
95 | }
96 | }
97 | }
98 | }
99 |
100 | return updates;
101 | }
102 |
103 | function HandleSnippets(website, websiteSnippets, records) {
104 | if (!websiteSnippets || websiteSnippets.length === 0) {
105 | return;
106 | }
107 |
108 | var record = {
109 | recid: website.adx_websiteid,
110 | schemaName: website.adx_name,
111 | w2ui: {
112 | editable: false,
113 | children: []
114 | }
115 | };
116 |
117 | var groupedSnippets = websiteSnippets.reduce(function(all, cur) {
118 | var key = website.adx_websiteid + idSeparator + cur.adx_name;
119 |
120 | if (all[key]) {
121 | all[key].push(cur);
122 | }
123 | else {
124 | all[key] = [ cur ];
125 | }
126 |
127 | return all;
128 | }, {});
129 |
130 | var keys = Object.keys(groupedSnippets);
131 |
132 | for (var j = 0; j < keys.length; j++) {
133 | var key = keys[j];
134 | var snippetsByGroup = groupedSnippets[key];
135 |
136 | var child = {
137 | recid: key,
138 | schemaName: key.substr(key.indexOf(idSeparator) + 1),
139 | w2ui: {
140 | hideCheckBox: true
141 | }
142 | };
143 |
144 | for (var k = 0; k < snippetsByGroup.length; k++) {
145 | var snippet = snippetsByGroup[k];
146 |
147 | if (!snippet.adx_contentsnippetlanguageid) {
148 | continue;
149 | }
150 |
151 | var websiteLanguage = portalLanguages.find(function(l) { return l.adx_websitelanguageid === snippet.adx_contentsnippetlanguageid.adx_websitelanguageid; });
152 |
153 | if (!websiteLanguage) {
154 | continue;
155 | }
156 |
157 | var language = websiteLanguage.adx_PortalLanguageId.adx_lcid.toString();
158 | child[language] = snippet.adx_value;
159 | }
160 |
161 | record.w2ui.children.push(child);
162 | }
163 |
164 | records.push(record);
165 | }
166 |
167 | function FillTable () {
168 | var grid = XrmTranslator.GetGrid();
169 | grid.clear();
170 |
171 | var records = [];
172 | var keys = Object.keys(websites);
173 |
174 | for (var i = 0; i < keys.length; i++) {
175 | var website = websites[keys[i]];
176 | var websiteSnippets = snippets.filter(function(s) { return s.adx_websiteid && s.adx_websiteid.adx_websiteid === keys[i] });
177 |
178 | HandleSnippets(website, websiteSnippets, records);
179 | }
180 |
181 | XrmTranslator.AddSummary(records);
182 | grid.add(records);
183 | grid.unlock();
184 | }
185 |
186 | ContentSnippetHandler.Load = function () {
187 | XrmTranslator.columnRestoreNeeded = true;
188 |
189 | snippets = [];
190 | websites = [];
191 | portalLanguages = [];
192 |
193 | XrmTranslator.ClearColumns();
194 |
195 | return TranslationHandler.FindPortalLanguages()
196 | .then(function(languages) {
197 | if (languages) {
198 | portalLanguages = languages;
199 | TranslationHandler.FillPortalLanguageCodes(portalLanguages);
200 | }
201 |
202 | return WebApiClient.Retrieve({entityName: "adx_contentsnippet", queryParams: "?$select=adx_name,adx_value&$filter=_adx_contentsnippetlanguageid_value ne null&$expand=adx_websiteid($select=adx_websiteid,adx_name),adx_contentsnippetlanguageid($select=adx_websitelanguageid)&$orderby=adx_name", returnAllPages: true });
203 | })
204 | .then(function(response) {
205 | snippets = response.value.map(function (s) { s.adx_value = w2utils.encodeTags(s.adx_value); return s; });
206 | websites = snippets.reduce(function(all, cur) {
207 | if (!all[cur.adx_websiteid.adx_websiteid]) {
208 | all[cur.adx_websiteid.adx_websiteid] = cur.adx_websiteid;
209 | }
210 |
211 | return all;
212 | }, {});
213 |
214 | FillTable()
215 | })
216 | .catch(XrmTranslator.errorHandler);
217 | }
218 |
219 | ContentSnippetHandler.Save = function() {
220 | XrmTranslator.LockGrid("Saving");
221 |
222 | var records = XrmTranslator.GetAllRecords();
223 | var updates = GetUpdates(records);
224 |
225 | if (!updates || updates.length === 0) {
226 | XrmTranslator.LockGrid("Reloading");
227 |
228 | return ContentSnippetHandler.Load();
229 | }
230 |
231 | return WebApiClient.Promise.resolve(updates)
232 | .each(function(payload) {
233 | if (payload.entityId) {
234 | return WebApiClient.Update(payload);
235 | }
236 | else {
237 | return WebApiClient.Create(payload);
238 | }
239 | })
240 | .then(function(response) {
241 | return XrmTranslator.ReleaseLockAndPrompt();
242 | })
243 | .then(function (response) {
244 | XrmTranslator.LockGrid("Reloading");
245 |
246 | return ContentSnippetHandler.Load();
247 | })
248 | .catch(XrmTranslator.errorHandler);
249 | }
250 | } (window.ContentSnippetHandler = window.ContentSnippetHandler || {}));
251 |
--------------------------------------------------------------------------------
/src/js/PropertyEditor/XrmPropertyEditor.js:
--------------------------------------------------------------------------------
1 | /* @preserve
2 | * MIT License
3 | *
4 | * Copyright (c) 2017 Florian Krönert
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | */
25 | (function (XrmPropertyEditor, undefined) {
26 | "use strict";
27 |
28 | XrmPropertyEditor.entityMetadata = {};
29 | XrmPropertyEditor.metadata = [];
30 |
31 | XrmPropertyEditor.entity = null;
32 | XrmPropertyEditor.type = null;
33 |
34 | var currentHandler = null;
35 | var initialColumns = [
36 | { field: 'schemaName', caption: 'Schema Name', size: '20%', sortable: true, resizable: true, frozen: true }
37 | ];
38 |
39 | XrmPropertyEditor.RestoreInitialColumns = function () {
40 | var grid = XrmPropertyEditor.GetGrid();
41 |
42 | grid.columns = initialColumns;
43 |
44 | grid.refresh();
45 | };
46 |
47 | XrmPropertyEditor.GetEntity = function() {
48 | return w2ui.grid_toolbar.get("entitySelect").selected;
49 | }
50 |
51 | XrmPropertyEditor.GetEntityId = function() {
52 | return XrmPropertyEditor.entityMetadata[XrmPropertyEditor.GetEntity()]
53 | }
54 |
55 | XrmPropertyEditor.GetType = function() {
56 | return w2ui.grid_toolbar.get("type").selected;
57 | }
58 |
59 | function SetHandler() {
60 | if (XrmPropertyEditor.GetType() === "attributes") {
61 | currentHandler = AttributePropertyHandler;
62 | }
63 | else if (XrmPropertyEditor.GetType() === "entities") {
64 | currentHandler = EntityPropertyHandler;
65 | }
66 | }
67 |
68 | XrmPropertyEditor.errorHandler = function(error) {
69 | if(error.statusText) {
70 | w2alert(error.statusText);
71 | }
72 | else {
73 | w2alert(error);
74 | }
75 |
76 | XrmPropertyEditor.UnlockGrid();
77 | }
78 |
79 | XrmPropertyEditor.SchemaNameComparer = function(e1, e2) {
80 | if (e1.SchemaName < e2.SchemaName) {
81 | return -1;
82 | }
83 |
84 | if (e1.SchemaName > e2.SchemaName) {
85 | return 1;
86 | }
87 |
88 | return 0;
89 | }
90 |
91 | XrmPropertyEditor.GetGrid = function() {
92 | return w2ui.grid;
93 | }
94 |
95 | XrmPropertyEditor.LockGrid = function (message) {
96 | XrmPropertyEditor.GetGrid().lock(message, true);
97 | }
98 |
99 | XrmPropertyEditor.UnlockGrid = function () {
100 | XrmPropertyEditor.GetGrid().unlock();
101 | }
102 |
103 | XrmPropertyEditor.Publish = function() {
104 | var xml = "" + XrmPropertyEditor.GetEntity().toLowerCase() + "";
105 |
106 | var request = WebApiClient.Requests.PublishXmlRequest
107 | .with({
108 | payload: {
109 | ParameterXml: xml
110 | }
111 | })
112 | return WebApiClient.Execute(request);
113 | }
114 |
115 | XrmPropertyEditor.GetRecord = function(records, selector) {
116 | for (var i = 0; i < records.length; i++) {
117 | var record = records[i];
118 |
119 | if (selector(record)) {
120 | return record;
121 | }
122 | }
123 |
124 | return null;
125 | }
126 |
127 | XrmPropertyEditor.SetSaveButtonDisabled = function (disabled) {
128 | var saveButton = w2ui.grid_toolbar.get("w2ui-save");
129 | saveButton.disabled = disabled;
130 | w2ui.grid_toolbar.refresh();
131 | }
132 |
133 | XrmPropertyEditor.GetAttributeById = function(id) {
134 | return XrmPropertyEditor.GetAttributeByProperty("MetadataId", id);
135 | }
136 |
137 | XrmPropertyEditor.GetByRecId = function (records, recid) {
138 | function selector(rec) {
139 | if (rec.recid === recid) {
140 | return true;
141 | }
142 | return false;
143 | }
144 |
145 | return XrmPropertyEditor.GetRecord(records, selector);
146 | };
147 |
148 | XrmPropertyEditor.GetAttributeByProperty = function(property, value) {
149 | for (var i = 0; i < XrmPropertyEditor.metadata.length; i++) {
150 | var attribute = XrmPropertyEditor.metadata[i];
151 |
152 | if (attribute[property] === value) {
153 | return attribute;
154 | }
155 | }
156 |
157 | return null;
158 | }
159 |
160 | function InitializeGrid (entities) {
161 | $('#grid').w2grid({
162 | name: 'grid',
163 | show: {
164 | toolbar: true,
165 | footer: true,
166 | toolbarSave: true,
167 | toolbarSearch: true
168 | },
169 | multiSearch: true,
170 | searches: [
171 | { field: 'schemaName', caption: 'Schema Name', type: 'text' }
172 | ],
173 | columns: initialColumns,
174 | onSave: function (event) {
175 | currentHandler.Save();
176 | },
177 | toolbar: {
178 | items: [
179 | { type: 'menu-radio', id: 'entitySelect', img: 'icon-folder',
180 | text: function (item) {
181 | var text = item.selected;
182 | var el = this.get('entitySelect:' + item.selected);
183 |
184 | if (el) {
185 | return 'Entity: ' + el.text;
186 | }
187 | else {
188 | return "Choose entity";
189 | }
190 | },
191 | items: []
192 | },
193 | { type: 'menu-radio', id: 'type', img: 'icon-folder',
194 | text: function (item) {
195 | var text = item.selected;
196 | var el = this.get('type:' + item.selected);
197 | return 'Type: ' + el.text;
198 | },
199 | selected: 'attributes',
200 | items: [
201 | { id: 'attributes', text: 'Attributes', icon: 'fa-camera' }
202 | //{ id: 'entities', text: 'Entities', icon: 'fa-picture' }
203 | ]
204 | },
205 | { type: 'button', id: 'load', text: 'Load', img:'w2ui-icon-reload', onClick: function (event) {
206 | var entity = XrmPropertyEditor.GetEntity();
207 |
208 | if (!entity || !XrmPropertyEditor.GetType()) {
209 | return;
210 | }
211 |
212 | SetHandler();
213 |
214 | XrmPropertyEditor.LockGrid("Loading " + entity + " attributes");
215 |
216 | currentHandler.Load();
217 | } }
218 | ]
219 | }
220 | });
221 |
222 | XrmPropertyEditor.LockGrid("Loading entities");
223 | }
224 |
225 | function FillEntitySelector (entities) {
226 | entities = entities.sort(XrmPropertyEditor.SchemaNameComparer);
227 | var entitySelect = w2ui.grid_toolbar.get("entitySelect").items;
228 |
229 | for (var i = 0; i < entities.length; i++) {
230 | var entity = entities[i];
231 |
232 | entitySelect.push(entity.SchemaName);
233 | XrmPropertyEditor.entityMetadata[entity.SchemaName] = entity.MetadataId;
234 | }
235 |
236 | return entities;
237 | }
238 |
239 | function GetEntities() {
240 | var request = {
241 | entityName: "EntityDefinition",
242 | queryParams: "?$select=SchemaName,MetadataId&$filter=IsCustomizable/Value eq true"
243 | };
244 |
245 | return WebApiClient.Retrieve(request);
246 | }
247 |
248 | function RegisterReloadPrevention () {
249 | // Dashboards are automatically refreshed on browser window resize, we don't want to loose changes.
250 | window.onbeforeunload = function(e) {
251 | var records = XrmPropertyEditor.GetGrid().records;
252 | var unsavedChanges = false;
253 |
254 | for (var i = 0; i < records.length; i++) {
255 | var record = records[i];
256 |
257 | if (record.w2ui && record.w2ui.changes) {
258 | unsavedChanges = true;
259 | break;
260 | }
261 | }
262 |
263 | if (unsavedChanges) {
264 | var warning = "There are unsaved changes in the dashboard, are you sure you want to reload and discard changes?";
265 | e.returnValue = warning;
266 | return warning;
267 | }
268 | };
269 | }
270 |
271 | XrmPropertyEditor.Initialize = function() {
272 | InitializeGrid();
273 | RegisterReloadPrevention();
274 |
275 | GetEntities()
276 | .then(function(response) {
277 | return FillEntitySelector(response.value);
278 | })
279 | .then(function () {
280 | XrmPropertyEditor.UnlockGrid();
281 | })
282 | .catch(XrmPropertyEditor.errorHandler);
283 | }
284 | } (window.XrmPropertyEditor = window.XrmPropertyEditor || {}));
285 |
--------------------------------------------------------------------------------
/src/js/Translator/OptionSetHandler.js:
--------------------------------------------------------------------------------
1 | /* @preserve
2 | * MIT License
3 | *
4 | * Copyright (c) 2017 Florian Krönert
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | */
25 | (function (OptionSetHandler, undefined) {
26 | "use strict";
27 | var idSeparator = "|";
28 |
29 | function GetRecordId (id) {
30 | var separatorIndex = id.indexOf(idSeparator);
31 |
32 | if (separatorIndex === -1) {
33 | return id;
34 | }
35 |
36 | return id.substring(0, separatorIndex);
37 | }
38 |
39 | function GetComponent () {
40 | var component = XrmTranslator.GetComponent();
41 |
42 | if (component === "DisplayName") {
43 | return "Label";
44 | }
45 |
46 | return component;
47 | }
48 |
49 | function GetOptionValueUpdate (attribute, value, labels) {
50 | if (!attribute.GlobalOptionSet && !attribute.OptionSet) {
51 | throw new Error("Either the global option set or the OptionSet have to be passed!");
52 | }
53 |
54 | var updateComponent = GetComponent();
55 |
56 | var update = {
57 | Value: value,
58 | [updateComponent]: {
59 | LocalizedLabels: labels
60 | },
61 | MergeLabels: true
62 | };
63 |
64 | if (attribute.GlobalOptionSet && attribute.GlobalOptionSet.IsGlobal) {
65 | update.OptionSetName = attribute.GlobalOptionSet.Name;
66 | }
67 | else {
68 | update.EntityLogicalName = XrmTranslator.GetEntity().toLowerCase();
69 | update.AttributeLogicalName = attribute.LogicalName;
70 | }
71 |
72 | return update;
73 | }
74 |
75 | function GetUpdateIds(records) {
76 | var optionSets = [];
77 | var globalOptionSets = [];
78 | var globalOptionSetNames = [];
79 |
80 | for (var i = 0; i < records.length; i++) {
81 | var record = records[i];
82 |
83 | if (record.w2ui && record.w2ui.changes) {
84 | var recordId = GetRecordId(record.recid);
85 | var attribute = XrmTranslator.GetAttributeById (recordId);
86 |
87 | if (attribute.GlobalOptionSet && attribute.GlobalOptionSet.IsGlobal) {
88 | if (globalOptionSets.indexOf(attribute.GlobalOptionSet.MetadataId) === -1) {
89 | globalOptionSets.push(attribute.GlobalOptionSet.MetadataId);
90 | globalOptionSetNames.push(attribute.GlobalOptionSet.Name);
91 | }
92 | }
93 | else {
94 | if (optionSets.indexOf(recordId) === -1) {
95 | optionSets.push(recordId);
96 | }
97 | }
98 | }
99 | }
100 |
101 | return [optionSets, globalOptionSets, globalOptionSetNames];
102 | }
103 |
104 | function GetUpdates(records) {
105 | var updates = [];
106 |
107 | for (var i = 0; i < records.length; i++) {
108 | var record = records[i];
109 |
110 | if (record.w2ui && record.w2ui.changes) {
111 | var recordId = GetRecordId(record.recid);
112 | var attribute = XrmTranslator.GetAttributeById (recordId);
113 | var optionSetValue = parseInt(record.schemaName);
114 | var changes = record.w2ui.changes;
115 |
116 | if (optionSetValue === null || typeof(optionSetValue) === "undefined") {
117 | continue;
118 | }
119 |
120 | var labels = [];
121 |
122 | for (var change in changes) {
123 | if (!changes.hasOwnProperty(change)) {
124 | continue;
125 | }
126 |
127 | // Skip empty labels
128 | if (!changes[change]) {
129 | continue;
130 | }
131 |
132 | var label = { LanguageCode: change, Label: changes[change] };
133 |
134 | labels.push(label);
135 | }
136 |
137 | if (labels.length < 1) {
138 | continue;
139 | }
140 |
141 | var update = GetOptionValueUpdate(attribute, optionSetValue, labels);
142 | updates.push(update);
143 | }
144 | }
145 |
146 | return updates;
147 | }
148 |
149 | function HandleOptionSets(attribute, options, records) {
150 | if (!options || options.length === 0) {
151 | return;
152 | }
153 |
154 | var record = {
155 | recid: attribute.MetadataId,
156 | schemaName: attribute.LogicalName,
157 | w2ui: {
158 | editable: false,
159 | children: []
160 | }
161 | };
162 |
163 | for (var j = 0; j < options.length; j++) {
164 | var option = options[j];
165 | var labels = option[GetComponent()].LocalizedLabels;
166 |
167 | var child = {
168 | recid: record.recid + idSeparator + option.Value,
169 | schemaName: option.Value
170 | };
171 |
172 | for (var k = 0; k < labels.length; k++) {
173 | var label = labels[k];
174 | child[label.LanguageCode.toString()] = label.Label;
175 | }
176 |
177 | record.w2ui.children.push(child);
178 | }
179 |
180 | records.push(record);
181 | }
182 |
183 | function FillTable () {
184 | var grid = XrmTranslator.GetGrid();
185 | grid.clear();
186 |
187 | var records = [];
188 |
189 | for (var i = 0; i < XrmTranslator.metadata.length; i++) {
190 | var attribute = XrmTranslator.metadata[i];
191 | var optionSet = attribute.OptionSet;
192 |
193 | if (!optionSet) {
194 | optionSet = attribute.GlobalOptionSet;
195 | }
196 |
197 | if (!!optionSet.TrueOption) {
198 | HandleOptionSets(attribute, [optionSet.TrueOption, optionSet.FalseOption], records);
199 | }
200 | else {
201 | var options = optionSet.Options;
202 |
203 | HandleOptionSets(attribute, options, records);
204 | }
205 | }
206 |
207 | XrmTranslator.AddSummary(records);
208 | grid.add(records);
209 | grid.unlock();
210 | }
211 |
212 | OptionSetHandler.Load = function () {
213 | var entityName = XrmTranslator.GetEntity();
214 | var entityMetadataId = XrmTranslator.entityMetadata[entityName];
215 |
216 | var optionSetRequest = {
217 | entityName: "EntityDefinition",
218 | entityId: entityMetadataId,
219 | queryParams: "/Attributes/Microsoft.Dynamics.CRM.PicklistAttributeMetadata?$expand=OptionSet,GlobalOptionSet"
220 | };
221 |
222 | var booleanRequest = {
223 | entityName: "EntityDefinition",
224 | entityId: entityMetadataId,
225 | queryParams: "/Attributes/Microsoft.Dynamics.CRM.BooleanAttributeMetadata?$expand=OptionSet,GlobalOptionSet"
226 | };
227 |
228 | var statusRequest = {
229 | entityName: "EntityDefinition",
230 | entityId: entityMetadataId,
231 | queryParams: "/Attributes/Microsoft.Dynamics.CRM.StatusAttributeMetadata?$expand=OptionSet,GlobalOptionSet"
232 | };
233 |
234 | var multiOptionSetRequest = {
235 | entityName: "EntityDefinition",
236 | entityId: entityMetadataId,
237 | queryParams: "/Attributes/Microsoft.Dynamics.CRM.MultiSelectPicklistAttributeMetadata?$expand=OptionSet,GlobalOptionSet"
238 | };
239 |
240 | return WebApiClient.Promise.all([WebApiClient.Retrieve(optionSetRequest), WebApiClient.Retrieve(booleanRequest), WebApiClient.Retrieve(statusRequest), WebApiClient.Retrieve(multiOptionSetRequest)])
241 | .then(function(responses){
242 | var responseValues = responses[0].value.concat(responses[1].value).concat(responses[2].value).concat(responses[3].value);
243 | var attributes = responseValues.sort(XrmTranslator.SchemaNameComparer);
244 |
245 | XrmTranslator.metadata = attributes;
246 |
247 | FillTable();
248 | })
249 | .catch(XrmTranslator.errorHandler);
250 | }
251 |
252 | OptionSetHandler.Save = function() {
253 | XrmTranslator.LockGrid("Saving");
254 |
255 | var records = XrmTranslator.GetAllRecords();
256 | var updates = GetUpdates(records);
257 | var updateIds = GetUpdateIds(records);
258 |
259 | if (!updates || updates.length === 0) {
260 | XrmTranslator.LockGrid("Reloading");
261 |
262 | return OptionSetHandler.Load();
263 | }
264 |
265 | return WebApiClient.Promise.resolve(updates)
266 | .each(function(payload) {
267 | return WebApiClient.SendRequest("POST", WebApiClient.GetApiUrl() + "UpdateOptionValue", payload);
268 | })
269 | .then(function (response){
270 | XrmTranslator.LockGrid("Publishing");
271 |
272 | return XrmTranslator.Publish(updateIds[2]);
273 | })
274 | .then(function(response) {
275 | return Promise.all([
276 | XrmTranslator.AddToSolution(updateIds[0], XrmTranslator.ComponentType.Attribute),
277 | XrmTranslator.AddToSolution(updateIds[1], XrmTranslator.ComponentType.OptionSet, true, true)
278 | ])
279 | })
280 | .then(function(response) {
281 | return XrmTranslator.ReleaseLockAndPrompt();
282 | })
283 | .then(function (response) {
284 | XrmTranslator.LockGrid("Reloading");
285 |
286 | return OptionSetHandler.Load();
287 | })
288 | .catch(XrmTranslator.errorHandler);
289 | }
290 | } (window.OptionSetHandler = window.OptionSetHandler || {}));
291 |
--------------------------------------------------------------------------------
/src/js/Translator/WebResourceHandler.js:
--------------------------------------------------------------------------------
1 | /* @preserve
2 | * MIT License
3 | *
4 | * Copyright (c) 2017 Florian Krönert
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | */
25 | (function (WebResourceHandler, undefined) {
26 | "use strict";
27 |
28 | var idSeparator = "|";
29 | var lcidRegex = /([0-9]+)\.js/i;
30 |
31 | function GetGroupKey (id) {
32 | var separatorIndex = id.indexOf(idSeparator);
33 |
34 | if (separatorIndex === -1) {
35 | return id;
36 | }
37 |
38 | return id.substring(0, separatorIndex);
39 | }
40 |
41 | function GetUpdates(records) {
42 | var updates = [];
43 |
44 | for (var i = 0; i < records.length; i++) {
45 | var record = records[i];
46 | var groupKey = GetGroupKey(record.recid);
47 |
48 | if (record.w2ui && record.w2ui.changes) {
49 | var group = XrmTranslator.metadata[groupKey];
50 | var property = record.schemaName;
51 |
52 | var changes = record.w2ui.changes;
53 |
54 | for (var change in changes) {
55 | if (!changes.hasOwnProperty(change)) {
56 | continue;
57 | }
58 |
59 | var updateRecord = group.find(function(w) { return w.__lcid === change }) || updates.find(function(w) { return w.__lcid === change });
60 |
61 | // In this case, we need to create a new web resource
62 | if (!updateRecord) {
63 | var baseLanguageRecord = group.find(function(w) { return w.__lcid == XrmTranslator.baseLanguage });
64 |
65 | updateRecord = {
66 | __lcid: change,
67 | webresourceid: undefined,
68 | name: baseLanguageRecord ? baseLanguageRecord.name.replace(XrmTranslator.baseLanguage.toString(), change) : groupKey + change + ".js",
69 | content: baseLanguageRecord ? Object.keys(baseLanguageRecord.content).reduce(function(all, cur) { all[cur] = null; return all; }, {}) : { }
70 | }
71 | }
72 |
73 | var value = changes[change];
74 | updateRecord.content[property] = w2utils.decodeTags(value);
75 |
76 | if (updates.indexOf(updateRecord) === -1) {
77 | updates.push(updateRecord);
78 | }
79 | }
80 | }
81 | }
82 |
83 | return updates;
84 | }
85 |
86 | function FillKey(record, property, group) {
87 | var keyRecord = {
88 | recid: record.recid + idSeparator + property,
89 | schemaName: property
90 | };
91 |
92 | for (var i = 0; i < group.length; i++) {
93 | var resource = group[i];
94 |
95 | var value = resource.content[property];
96 |
97 | if (!resource.__lcid) {
98 | continue;
99 | }
100 |
101 | keyRecord[resource.__lcid] = w2utils.encodeTags(value);
102 | }
103 |
104 | record.w2ui.children.push(keyRecord);
105 | }
106 |
107 | function FillTable () {
108 | var grid = XrmTranslator.GetGrid();
109 | grid.clear();
110 |
111 | var records = [];
112 |
113 | var groups = Object.keys(XrmTranslator.metadata);
114 |
115 | for (var i = 0; i < groups.length; i++) {
116 | var key = groups[i];
117 | var group = XrmTranslator.metadata[key];
118 |
119 | var record = {
120 | recid: key,
121 | schemaName: key,
122 | w2ui: {
123 | editable: false,
124 | children: []
125 | }
126 | };
127 |
128 | var properties = Array.from(new Set(group.map(function(g) { return Object.keys(g.content); }).reduce(function(all, cur) { return all.concat(cur); }, [])));
129 |
130 | for (var j = 0; j < properties.length; j++) {
131 | var property = properties[j];
132 |
133 | FillKey(record, property, group);
134 | }
135 |
136 | records.push(record);
137 | }
138 |
139 | XrmTranslator.AddSummary(records);
140 | grid.add(records);
141 | grid.unlock();
142 | }
143 |
144 | // https://stackoverflow.com/a/30106551
145 | function b64EncodeUnicode(str) {
146 | // first we use encodeURIComponent to get percent-encoded UTF-8,
147 | // then we convert the percent encodings into raw bytes which
148 | // can be fed into btoa.
149 | return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
150 | function toSolidBytes(match, p1) {
151 | return String.fromCharCode('0x' + p1);
152 | }));
153 | }
154 |
155 | // https://stackoverflow.com/a/30106551
156 | function b64DecodeUnicode(str) {
157 | // Going backwards: from bytestream, to percent-encoding, to original string.
158 | return decodeURIComponent(atob(str).split('').map(function(c) {
159 | return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
160 | }).join(''));
161 | }
162 |
163 | WebResourceHandler.Load = function() {
164 | XrmTranslator.GetBaseLanguage()
165 | .then(function(baseLanguage) {
166 | var request = {
167 | overriddenSetName: "webresourceset",
168 | queryParams: "?$select=webresourceid,name,content&$filter=contains(name, '" + baseLanguage + ".js')"
169 | };
170 |
171 | return WebApiClient.Promise.all([baseLanguage, WebApiClient.Retrieve(request)]);
172 | })
173 | .then(function(r) {
174 | var baseLanguage = r[0];
175 | var records = r[1].value;
176 |
177 | return WebApiClient.Promise.all(records.map(function(rec) {
178 | var groupingKey = rec.name.substr(0, rec.name.indexOf(baseLanguage));
179 |
180 | return WebApiClient.Retrieve({ overriddenSetName: "webresourceset", queryParams: "?$select=webresourceid,name,content&$filter=contains(name, '" + groupingKey + "')"})
181 | .then(function (g) {
182 | return {
183 | key: groupingKey,
184 | value: g.value.map(function(w) {
185 | try {
186 | var lcidMatches = w.name.match(lcidRegex);
187 | return Object.assign(w, { content: JSON.parse(b64DecodeUnicode(w.content)), __lcid: lcidMatches.length > 1 ? lcidMatches[1] : undefined });
188 | }
189 | catch {
190 | return {};
191 | }
192 | })
193 | };
194 | });
195 | }));
196 | })
197 | .then(function(responses) {
198 | var groupedResponses = responses.reduce(function(all, cur) {
199 | // Filter out resources that could not be parsed
200 | var resources = cur.value.filter(function(g) { return typeof(g.content) === "object" });
201 |
202 | if (resources.length > 0) {
203 | all[cur.key] = resources;
204 | }
205 |
206 | return all;
207 | }, {});
208 |
209 | XrmTranslator.metadata = groupedResponses;
210 |
211 | FillTable();
212 | })
213 | .catch(XrmTranslator.errorHandler);
214 | }
215 |
216 | WebResourceHandler.Save = function() {
217 | XrmTranslator.LockGrid("Saving");
218 |
219 | var records = XrmTranslator.GetAllRecords();
220 | var updates = GetUpdates(records);
221 |
222 | return WebApiClient.Promise.resolve(updates)
223 | .mapSeries(function(webresource) {
224 | var content = b64EncodeUnicode(JSON.stringify(webresource.content));
225 |
226 | if (webresource.webresourceid) {
227 | return WebApiClient.Update({
228 | overriddenSetName: "webresourceset",
229 | entityId: webresource.webresourceid,
230 | entity: {
231 | content: content
232 | }
233 | })
234 | .then(function() {
235 | return webresource.webresourceid;
236 | });
237 | }
238 | else {
239 | return WebApiClient.Create({
240 | overriddenSetName: "webresourceset",
241 | entity: {
242 | name: webresource.name,
243 | displayname: webresource.name,
244 | content: content,
245 | webresourcetype: 3
246 | }
247 | })
248 | .then(function(response) {
249 | // "Cut out" created Guid, response format is http://orgname/api/data/v8.0/webresourceset(49f117b8-287a-ea11-8106-0050568e4745)
250 | return response.substr(response.length - 37, 36)
251 | });
252 | }
253 | })
254 | .then(function (ids){
255 | XrmTranslator.LockGrid("Publishing");
256 |
257 | return XrmTranslator.PublishWebResources(ids)
258 | .then(function() {
259 | return ids;
260 | });
261 | })
262 | .then(function(ids) {
263 | // WebResources can't be added with defined componenent settings or DoNotIncludeSubcomponents flag set to true
264 | return XrmTranslator.AddToSolution(ids, XrmTranslator.ComponentType.WebResource, true, true);
265 | })
266 | .then(function(response) {
267 | return XrmTranslator.ReleaseLockAndPrompt();
268 | })
269 | .then(function (response) {
270 | XrmTranslator.LockGrid("Reloading");
271 |
272 | return WebResourceHandler.Load();
273 | })
274 | .catch(XrmTranslator.errorHandler);
275 | }
276 | } (window.WebResourceHandler = window.WebResourceHandler || {}));
277 |
--------------------------------------------------------------------------------
/src/js/Translator/FormHandler.js:
--------------------------------------------------------------------------------
1 | /* @preserve
2 | * MIT License
3 | *
4 | * Copyright (c) 2017 Florian Krönert
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | */
25 | (function (FormHandler, undefined) {
26 | "use strict";
27 |
28 | FormHandler.selectedForms = null;
29 | FormHandler.formsByLanguage = null;
30 | FormHandler.lastId = null;
31 |
32 | function GetParsedForm (form) {
33 | var parser = new DOMParser();
34 | var formXml = parser.parseFromString(form.formxml, "text/xml");
35 |
36 | return formXml;
37 | }
38 |
39 | function NodesWithIdAndLabels (node) {
40 | if (node.id && node.getElementsByTagName("labels").length > 0 && node.getElementsByTagName("control").length > 0) {
41 | return NodeFilter.FILTER_ACCEPT;
42 | }
43 | return NodeFilter.FILTER_SKIP;
44 | }
45 |
46 | function CreateTreeWalker(elementFilter, form, formXml) {
47 | if (!formXml) {
48 | formXml = GetParsedForm(form);
49 | }
50 | var treeWalker = document.createTreeWalker(formXml, NodeFilter.SHOW_ALL, elementFilter, false);
51 |
52 | return treeWalker;
53 | }
54 |
55 | function TraverseTree (treeWalker, tree) {
56 | // Dive down
57 | var child = CreateGridNode(treeWalker.firstChild());
58 |
59 | if (!child) {
60 | return;
61 | }
62 |
63 | // Push each first child per level
64 | tree.push(child);
65 | TraverseTree(treeWalker, child.w2ui.children);
66 |
67 | // We'll dive up level to level now and add all siblings
68 | while (treeWalker.nextSibling()) {
69 | var sibling = CreateGridNode(treeWalker.currentNode);
70 | tree.push(sibling);
71 | TraverseTree(treeWalker, sibling.w2ui.children);
72 | }
73 |
74 | treeWalker.parentNode();
75 | }
76 |
77 | function GetUpdates(records) {
78 | var updates = [];
79 |
80 | for (var i = 0; i < records.length; i++) {
81 | var record = records[i];
82 |
83 | if (record.w2ui && record.w2ui.changes) {
84 | var changes = record.w2ui.changes;
85 |
86 | var labels = [];
87 |
88 | for (var change in changes) {
89 | if (!changes.hasOwnProperty(change)) {
90 | continue;
91 | }
92 |
93 | // Skip empty labels
94 | if (!changes[change]) {
95 | continue;
96 | }
97 |
98 | var label = { LanguageCode: change, Text: changes[change] };
99 | labels.push(label);
100 | }
101 |
102 | if (labels.length < 1) {
103 | continue;
104 | }
105 |
106 | updates.push({
107 | id: record.recid,
108 | labels: labels
109 | });
110 | }
111 | }
112 |
113 | return updates;
114 | }
115 |
116 | function GetLabels(node) {
117 | var labelsNode = null;
118 | var children = node.children;
119 |
120 | for (var i = 0; i < children.length; i++) {
121 | var child = children[i];
122 |
123 | if (child && child.tagName === "labels") {
124 | labelsNode = child;
125 | break;
126 | }
127 | }
128 |
129 | return labelsNode;
130 | }
131 |
132 | function AttachLabels(node, gridNode) {
133 | var labels = GetLabels(node);
134 |
135 | if (!labels) {
136 | return;
137 | }
138 |
139 | for (var i = 0; i < labels.children.length; i++) {
140 | var label = labels.children[i];
141 |
142 | var text = label.attributes["description"].value;
143 | var languageCode = label.attributes["languagecode"].value;
144 |
145 | gridNode[languageCode] = text;
146 | }
147 | }
148 |
149 | function CreateGridNode (node) {
150 | if (!node) {
151 | return null;
152 | }
153 |
154 | var attributes = node.attributes;
155 | var name = "";
156 |
157 | if (attributes["name"]) {
158 | name = attributes["name"].value;
159 | }
160 | else {
161 | // var nodeid = attributes["id"].value;
162 | name = node.tagName;
163 | }
164 |
165 | var gridNode = {
166 | recid: node.id,
167 | schemaName: name,
168 | w2ui: {
169 | children: []
170 | }
171 | };
172 |
173 | if (XrmTranslator.config.lockFormCells && name.toLowerCase() === "cell") {
174 | gridNode.w2ui.editable = false;
175 | }
176 |
177 | AttachLabels(node, gridNode);
178 |
179 | for (var i = 0; i < FormHandler.selectedForms.length; i++) {
180 | var node = GetById(node.id, FormHandler.selectedForms[i]);
181 | AttachLabels(node, gridNode);
182 | }
183 |
184 | return gridNode;
185 | }
186 |
187 | function FillTable () {
188 | var grid = XrmTranslator.GetGrid();
189 | grid.clear();
190 |
191 | var records = [];
192 |
193 | var treeWalker = CreateTreeWalker(NodesWithIdAndLabels, XrmTranslator.metadata);
194 | TraverseTree(treeWalker, records);
195 |
196 | XrmTranslator.AddSummary(records, true);
197 | grid.add(records);
198 | grid.unlock();
199 | }
200 |
201 | function GetUserLanguageForm (forms) {
202 | for (var i = 0; i < forms.length; i++) {
203 | if (forms[i].languageCode === XrmTranslator.userSettings.uilanguageid) {
204 | return forms[i];
205 | }
206 | }
207 |
208 | return null;
209 | }
210 |
211 | function ProcessSelection(formId) {
212 | var formsByLanguage = FormHandler.formsByLanguage;
213 | var userLanguageForms = GetUserLanguageForm(formsByLanguage).forms.value;
214 |
215 | for (var i = 0; i < userLanguageForms.length; i++) {
216 | var languageForm = userLanguageForms[i];
217 |
218 | if (languageForm.formid === formId) {
219 | XrmTranslator.metadata = languageForm;
220 | break;
221 | }
222 | }
223 |
224 | FormHandler.selectedForms = [];
225 | for (var i = 0; i < formsByLanguage.length; i++) {
226 | var languageForms = formsByLanguage[i];
227 |
228 | for (var j = 0; j < languageForms.forms.value.length; j++) {
229 | var languageForm = languageForms.forms.value[j];
230 |
231 | if (languageForm.formid === formId) {
232 | FormHandler.selectedForms.push(languageForm);
233 | break;
234 | }
235 | }
236 | }
237 |
238 | FillTable();
239 | }
240 |
241 | function ShowFormSelection () {
242 | var formsByLanguage = FormHandler.formsByLanguage;
243 |
244 | if (!w2ui.formSelectionPrompt) {
245 | $().w2form({
246 | name: 'formSelectionPrompt',
247 | style: 'border: 0px; background-color: transparent;',
248 | formHTML:
249 | '
'+
250 | '
'+
251 | ' '+
252 | '
'+
253 | ' '+
254 | '
'+
255 | '
'+
256 | '
'+
257 | '
'+
258 | ' '+
259 | ' '+
260 | '
',
261 | fields: [
262 | { field: 'formSelection', type: 'list', required: true, html: {attr: 'style="width: 80%"'} }
263 | ],
264 | actions: {
265 | "ok": function () {
266 | this.validate();
267 |
268 | ProcessSelection(this.record.formSelection.id);
269 |
270 | w2popup.close();
271 | },
272 | "cancel": function () {
273 | XrmTranslator.UnlockGrid();
274 | w2popup.close();
275 | }
276 | }
277 | });
278 | }
279 |
280 | var userLanguageForms = GetUserLanguageForm(formsByLanguage).forms.value;
281 |
282 | var formItems = [];
283 |
284 | for (var i = 0; i < userLanguageForms.length; i++) {
285 | var form = userLanguageForms[i];
286 |
287 | formItems.push({
288 | id: form.formid,
289 | text: form.name + " - " + form.description
290 | });
291 | }
292 |
293 | w2ui.formSelectionPrompt.record.formSelection = null;
294 | w2ui.formSelectionPrompt.fields[0].options = { items: formItems };
295 |
296 | $().w2popup('open', {
297 | title : 'Choose Form',
298 | name : 'formSelectionPopup',
299 | body : '',
300 | style : 'padding: 15px 0px 0px 0px',
301 | width : 500,
302 | height : 300,
303 | showMax : true,
304 | onToggle: function (event) {
305 | $(w2ui.formSelection.box).hide();
306 | event.onComplete = function () {
307 | $(w2ui.formSelection.box).show();
308 | w2ui.formSelection.resize();
309 | }
310 | },
311 | onOpen: function (event) {
312 | event.onComplete = function () {
313 | // specifying an onOpen handler instead is equivalent to specifying an onBeforeOpen handler, which would make this code execute too early and hence not deliver.
314 | $('#w2ui-popup #form').w2render('formSelectionPrompt');
315 | }
316 | }
317 | });
318 | }
319 |
320 | function IdFilter (node) {
321 | if (node.id == this.id) {
322 | return NodeFilter.FILTER_ACCEPT;
323 | }
324 | return NodeFilter.FILTER_SKIP;
325 | }
326 |
327 | function GetById (id, form, formXml) {
328 | var treeWalker = CreateTreeWalker(IdFilter.bind({ id: id }), form, formXml);
329 |
330 | return treeWalker.nextNode();
331 | }
332 |
333 | function ApplyLabelUpdates (labels, updates, formXml) {
334 | for (var i = 0; i < updates.length; i++) {
335 | var update = updates[i];
336 |
337 | for (var j = 0; j < labels.children.length; j++) {
338 | var label = labels.children[j];
339 |
340 | if (update.LanguageCode === label.attributes["languagecode"].value) {
341 | label.attributes["description"].value = update.Text;
342 | }
343 | // We did not find it
344 | else if (j === labels.children.length - 1) {
345 | var newLabel = formXml.createElement("label");
346 |
347 | newLabel.setAttribute("description", update.Text);
348 | newLabel.setAttribute("languagecode", update.LanguageCode);
349 |
350 | labels.appendChild(newLabel);
351 | }
352 | }
353 | }
354 | }
355 |
356 | function SerializeXml(formXml) {
357 | var serializer = new XMLSerializer();
358 |
359 | return serializer.serializeToString(formXml);
360 | }
361 |
362 | function ApplyUpdates(updates, form, formXml) {
363 | for (var i = 0; i < updates.length; i++) {
364 | var update = updates[i];
365 |
366 | var node = GetById(update.id, form, formXml);
367 | var labels = GetLabels(node);
368 |
369 | ApplyLabelUpdates(labels, update.labels, formXml);
370 | }
371 |
372 | var serialized = SerializeXml(formXml);
373 |
374 | return {
375 | formxml: serialized
376 | };
377 | }
378 |
379 | function uuidv4() {
380 | return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
381 | (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
382 | );
383 | }
384 |
385 | FormHandler.RemoveOverriddenCellLabels = function() {
386 | if (!XrmTranslator.metadata || !XrmTranslator.metadata.formid) {
387 | return;
388 | }
389 |
390 | var formXml = GetParsedForm(XrmTranslator.metadata);
391 |
392 | Array.from(formXml.getElementsByTagName("cell"))
393 | .forEach(function(c) {
394 | const control = c.getElementsByTagName("control");
395 |
396 | // We only want to fix overridden labels for attributes, all attribute controls have a datafieldname
397 | if (!control || !control.length || !control[0].getAttribute("datafieldname")) {
398 | return;
399 | }
400 |
401 | // Regenerate cell id so that MS can't find the old overridden labels
402 | c.id = uuidv4();
403 |
404 | const labels = c.getElementsByTagName("labels");
405 |
406 | if(labels && labels.length) {
407 | const labelsNode = labels[0];
408 |
409 | // Remove all labels
410 | Array.from(labelsNode.getElementsByTagName("label")).forEach(function(l) { labelsNode.removeChild(l); });
411 | }
412 | });
413 |
414 | const serializer = new XMLSerializer();
415 | const payload = {
416 | formxml: serializer.serializeToString(formXml)
417 | };
418 |
419 | return FormHandler.Save(payload)
420 | .then(function() { alert("Successfully removed all cell labels!"); })
421 | .catch(function(e) { alert("Failed to remove cell labels: " + e.message); });
422 | };
423 |
424 | FormHandler.Load = function () {
425 | var entityName = XrmTranslator.GetEntity();
426 |
427 | var formRequest = {
428 | entityName: "systemform",
429 | queryParams: "?$filter=objecttypecode eq '" + entityName.toLowerCase() + "' and iscustomizable/Value eq true and formactivationstate eq 1"
430 | };
431 |
432 | if (entityName.toLowerCase() === "none") {
433 | formRequest.queryParams = "?$filter=formactivationstate eq 1 and iscustomizable/Value eq true and (type eq 0 or type eq 10)"
434 | }
435 |
436 | var languages = XrmTranslator.installedLanguages.LocaleIds;
437 | var initialLanguage = XrmTranslator.userSettings.uilanguageid;
438 | var forms = [];
439 | var requests = [];
440 |
441 | for (var i = 0; i < languages.length; i++) {
442 | requests.push({
443 | action: "Update",
444 | language: languages[i]
445 | });
446 |
447 | requests.push({
448 | action: "Retrieve",
449 | language: languages[i]
450 | });
451 | }
452 |
453 | requests.push({
454 | action: "Update",
455 | language: initialLanguage
456 | });
457 |
458 | return WebApiClient.Promise.reduce(requests, function(total, request){
459 | if (request.action === "Update") {
460 | return WebApiClient.Update({
461 | overriddenSetName: "usersettingscollection",
462 | entityId: XrmTranslator.userId,
463 | entity: { uilanguageid: request.language }
464 | })
465 | .then(function(response) {
466 | return total;
467 | });
468 | }
469 | else if (request.action === "Retrieve") {
470 | return WebApiClient.Promise.props({
471 | forms: WebApiClient.Retrieve(formRequest),
472 | languageCode: request.language
473 | })
474 | .then(function (response) {
475 | total.push(response);
476 |
477 | return total;
478 | });
479 | }
480 | }, [])
481 | .then(function(responses) {
482 | FormHandler.formsByLanguage = responses;
483 |
484 | if (FormHandler.lastId) {
485 | ProcessSelection(FormHandler.lastId);
486 | FormHandler.lastId = null;
487 | }
488 | else {
489 | ShowFormSelection();
490 | }
491 | })
492 | .catch(XrmTranslator.errorHandler);
493 | }
494 |
495 | FormHandler.Save = function(payload) {
496 | XrmTranslator.LockGrid("Saving");
497 |
498 | var update = undefined;
499 |
500 | if (payload) {
501 | update = payload;
502 | }
503 | else {
504 | var records = XrmTranslator.GetAllRecords();
505 | var formXml = GetParsedForm(XrmTranslator.metadata);
506 | var updates = GetUpdates(records);
507 |
508 | update = ApplyUpdates(updates, XrmTranslator.metadata, formXml);
509 | }
510 |
511 | return XrmTranslator.SetBaseLanguage(XrmTranslator.userId)
512 | .then(function() {
513 | return WebApiClient.Update({
514 | entityName: "systemform",
515 | entityId: XrmTranslator.metadata.formid,
516 | entity: update
517 | });
518 | })
519 | .then(function (response){
520 | XrmTranslator.LockGrid("Publishing");
521 | var entityName = XrmTranslator.GetEntity();
522 | if (entityName.toLowerCase() === "none") {
523 | return XrmTranslator.PublishDashboard([{ recid: XrmTranslator.metadata.formid }]);
524 | }
525 | else {
526 | return XrmTranslator.Publish();
527 | }
528 | })
529 | .then(function(response) {
530 | if (XrmTranslator.GetEntity().toLowerCase() === "none") {
531 | // Dashboards can't be added with defined componenent settings or DoNotIncludeSubcomponents flag set to true
532 | return XrmTranslator.AddToSolution([XrmTranslator.metadata.formid], XrmTranslator.ComponentType.SystemForm, true, true);
533 | }
534 | else {
535 | return XrmTranslator.AddToSolution([XrmTranslator.metadata.formid], XrmTranslator.ComponentType.SystemForm);
536 | }
537 | })
538 | .then(function(response) {
539 | return XrmTranslator.ReleaseLockAndPrompt();
540 | })
541 | .then(function (response) {
542 | XrmTranslator.LockGrid("Reloading");
543 |
544 | FormHandler.lastId = XrmTranslator.metadata.formid;
545 | return FormHandler.Load();
546 | })
547 | .catch(XrmTranslator.errorHandler);
548 | }
549 | } (window.FormHandler = window.FormHandler || {}));
550 |
--------------------------------------------------------------------------------
/src/js/Translator/TranslationHandler.js:
--------------------------------------------------------------------------------
1 | /* @preserve
2 | * MIT License
3 | *
4 | * Copyright (c) 2017 Florian Krönert
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | */
25 | (function (TranslationHandler, undefined) {
26 | "use strict";
27 |
28 | var locales = null;
29 |
30 | function GetLanguageIsoByLcid (lcid) {
31 | var locByLocales = locales.find(function(loc) { return loc.localeid === lcid; });
32 |
33 | if (locByLocales) {
34 | return locByLocales.code.substr(0, 2);
35 | }
36 |
37 | var locByColumns = XrmTranslator.GetGrid().columns.find(function(c) { return c.field === lcid});
38 |
39 | if (locByColumns) {
40 | return locByColumns.caption.substr(0, 2);
41 | }
42 |
43 | return null;
44 | }
45 |
46 | const deeplTranslator = function (authKey) {
47 | var baseUrl = "https://api.deepl.com/v2";
48 | var translationApiUrl = baseUrl + "/translate?auth_key=[auth_key]&source_lang=[source_lang]&target_lang=[target_lang]&text=[text]&tag_handling=xml";
49 |
50 | function BuildTranslationUrl (fromLanguage, destLanguage, phrase) {
51 | return translationApiUrl
52 | .replace("[auth_key]", authKey)
53 | .replace("[source_lang]", fromLanguage)
54 | .replace("[target_lang]", destLanguage)
55 | .replace("[text]", encodeURIComponent(phrase));
56 | }
57 |
58 | this.GetTranslation = function(fromLanguage, destLanguage, phrase) {
59 | $.support.cors = true;
60 |
61 | return WebApiClient.Promise.resolve($.ajax({
62 | url: BuildTranslationUrl(fromLanguage, destLanguage, phrase),
63 | type: "GET",
64 | crossDomain: true,
65 | dataType: "json"
66 | }));
67 | }
68 |
69 | this.AddTranslations = function(fromLcid, destLcid, updateRecords, responses) {
70 | var translations = [];
71 |
72 | for (var i = 0; i < updateRecords.length; i++) {
73 | var response = responses[i];
74 | var updateRecord = updateRecords[i];
75 |
76 | if (response.translations.length > 0) {
77 | var decoded = response.translations[0].text.replace(/))"\/>/gi, "$1");
78 | var translation = w2utils.encodeTags(decoded);
79 |
80 | var record = XrmTranslator.GetByRecId(updateRecords, updateRecord.recid);
81 |
82 | if (!record) {
83 | continue;
84 | }
85 |
86 | translations.push({
87 | recid: record.recid,
88 | schemaName: record.schemaName,
89 | column: destLcid,
90 | source: record[fromLcid],
91 | translation: translation
92 | });
93 | }
94 | }
95 |
96 | return translations;
97 | }
98 |
99 | this.CanTranslate = function(fromLcid, destLcid) {
100 | $.support.cors = true;
101 |
102 | return WebApiClient.Promise.resolve($.ajax({
103 | url: baseUrl + "/languages?auth_key=" + authKey,
104 | type: "GET",
105 | crossDomain: true,
106 | dataType: "json"
107 | }))
108 | .then(function(result) {
109 | const canTranslateSource = result.some(function (l) {
110 | return l.language.toLowerCase() === fromLcid.toLowerCase()
111 | });
112 |
113 | const canTranslateTarget = result.some(function (l) {
114 | return l.language.toLowerCase() === destLcid.toLowerCase()
115 | });
116 |
117 | return {
118 | [fromLcid]: canTranslateSource,
119 | [destLcid]: canTranslateTarget
120 | };
121 | });
122 | }
123 | };
124 |
125 | const azureTranslator = function (authKey, region) {
126 | var baseUrl = "https://api.cognitive.microsofttranslator.com";
127 | var translationApiUrl = baseUrl + "/translate?api-version=3.0&from=[source_lang]&to=[target_lang]&textType=html";
128 | var languageUrl = baseUrl + "/languages?api-version=3.0";
129 |
130 | function BuildTranslationUrl (fromLanguage, destLanguage) {
131 | return translationApiUrl
132 | .replace("[source_lang]", fromLanguage)
133 | .replace("[target_lang]", destLanguage);
134 | }
135 |
136 | this.GetTranslation = function(fromLanguage, destLanguage, phrase) {
137 | $.support.cors = true;
138 |
139 | const headers = {
140 | "Ocp-Apim-Subscription-Key": authKey
141 | };
142 |
143 | if (region) {
144 | headers["Ocp-Apim-Subscription-Region"] = region;
145 | }
146 |
147 | return WebApiClient.Promise.resolve($.ajax({
148 | url: BuildTranslationUrl(fromLanguage, destLanguage),
149 | dataType: "json",
150 | contentType: "application/json",
151 | type: "POST",
152 | data: JSON.stringify([{"Text":phrase}]),
153 | crossDomain: true,
154 | dataType: "json",
155 | headers: headers
156 | }));
157 | }
158 |
159 | this.AddTranslations = function(fromLcid, destLcid, updateRecords, responses) {
160 | var translations = [];
161 |
162 | for (var i = 0; i < updateRecords.length; i++) {
163 | var response = responses[i][0];
164 | var updateRecord = updateRecords[i];
165 |
166 | if (!response) {
167 | continue;
168 | }
169 |
170 | if (response.translations.length > 0) {
171 | var decoded = response.translations[0].text.replace(/))"\/>/gi, "$1");
172 | var translation = w2utils.encodeTags(decoded);
173 |
174 | var record = XrmTranslator.GetByRecId(updateRecords, updateRecord.recid);
175 |
176 | if (!record) {
177 | continue;
178 | }
179 |
180 | translations.push({
181 | recid: record.recid,
182 | schemaName: record.schemaName,
183 | column: destLcid,
184 | source: record[fromLcid],
185 | translation: translation
186 | });
187 | }
188 | }
189 |
190 | return translations;
191 | }
192 |
193 | this.CanTranslate = function(fromLcid, destLcid) {
194 | $.support.cors = true;
195 |
196 | return WebApiClient.Promise.resolve($.ajax({
197 | url: languageUrl,
198 | dataType: "json",
199 | type: "GET",
200 | crossDomain: true,
201 | headers: {
202 | "Ocp-Apim-Subscription-Key": authKey
203 | }
204 | }))
205 | .then(function(result) {
206 | const canTranslateSource = !!result.translation[fromLcid.toLowerCase()];
207 | const canTranslateTarget = !!result.translation[destLcid.toLowerCase()];
208 |
209 | return {
210 | [fromLcid]: canTranslateSource,
211 | [destLcid]: canTranslateTarget
212 | };
213 | });
214 | }
215 | };
216 |
217 | TranslationHandler.ApplyTranslations = function (selected, results) {
218 | var grid = XrmTranslator.GetGrid();
219 | var savable = false;
220 |
221 | for (var i = 0; i < selected.length; i++) {
222 | var select = selected[i];
223 |
224 | var result = XrmTranslator.GetByRecId(results, select);
225 | var record = XrmTranslator.GetByRecId(XrmTranslator.GetAllRecords(), result.recid);
226 |
227 | if (!record) {
228 | continue;
229 | }
230 |
231 | if (!record.w2ui) {
232 | record["w2ui"] = {};
233 | }
234 |
235 | if (!record.w2ui.changes) {
236 | record.w2ui["changes"] = {};
237 | }
238 |
239 | record.w2ui.changes[result.column] = (result.w2ui &&result.w2ui.changes) ? result.w2ui.changes.translation : result.translation;
240 | savable = true;
241 | grid.refreshRow(record.recid);
242 | }
243 |
244 | if (savable) {
245 | XrmTranslator.SetSaveButtonDisabled(false);
246 | }
247 | }
248 |
249 | function ShowTranslationResults (results) {
250 | if (!w2ui.translationResultGrid) {
251 | var grid = {
252 | name: 'translationResultGrid',
253 | show: { selectColumn: true },
254 | multiSelect: true,
255 | columns: [
256 | { field: 'schemaName', caption: 'Schema Name', size: '25%', sortable: true, searchable: true },
257 | { field: 'column', caption: 'Column LCID', sortable: true, searchable: true, hidden: true },
258 | { field: 'source', caption: 'Source Text', size: '25%', sortable: true, searchable: true },
259 | { field: 'translation', caption: 'Translated Text', size: '25%', sortable: true, searchable: true, editable: { type: 'text' } }
260 | ],
261 | records: []
262 | };
263 |
264 | $(function () {
265 | // initialization in memory
266 | $().w2grid(grid);
267 | });
268 | }
269 |
270 | w2ui.translationResultGrid.clear();
271 | w2ui.translationResultGrid.add(results);
272 |
273 | w2popup.open({
274 | title : 'Apply Translation Results',
275 | buttons : ' '+
276 | '',
277 | width : 900,
278 | height : 600,
279 | showMax : true,
280 | body : '',
281 | onOpen : function (event) {
282 | event.onComplete = function () {
283 | $('#w2ui-popup #main').w2render('translationResultGrid');
284 | w2ui.translationResultGrid.selectAll();
285 | };
286 | },
287 | onToggle: function (event) {
288 | $(w2ui.translationResultGrid.box).hide();
289 | event.onComplete = function () {
290 | $(w2ui.translationResultGrid.box).show();
291 | w2ui.translationResultGrid.resize();
292 | }
293 | }
294 | });
295 | }
296 |
297 | function CreateTranslator (apiProvider, authKey, region) {
298 | switch ((apiProvider ||"").trim().toLowerCase()) {
299 | case "deepl":
300 | return new deeplTranslator(authKey);
301 | case "azure":
302 | return new azureTranslator(authKey, region);
303 | default:
304 | return null;
305 | }
306 | }
307 |
308 | function BuildError(preFallBackError, error) {
309 | return [preFallBackError, error]
310 | .filter(function(e) { return !!e })
311 | .join(" ");
312 | }
313 |
314 | function FindTranslator(authKey, authProvider, region, fromLcid, destLcid, apiProvider, preFallBackError) {
315 | if(apiProvider !== "auto" && (authProvider ||"").trim().toLowerCase() !== apiProvider) {
316 | return WebApiClient.Promise.resolve([null, BuildError(preFallBackError, "")]);
317 | }
318 |
319 | if (!authKey) {
320 | XrmTranslator.UnlockGrid();
321 | return WebApiClient.Promise.resolve([null, BuildError(preFallBackError, authProvider + ": Auth Key is missing, please add one in the config web resource")]);
322 | }
323 |
324 | var translator = CreateTranslator(authProvider, authKey, region);
325 |
326 | if (!translator) {
327 | XrmTranslator.UnlockGrid();
328 | return WebApiClient.Promise.resolve([null, BuildError(preFallBackError, authProvider + ": Found not supported or missing API Provider, please set one in the config web resource (currently only 'deepl' and 'azure' are supported")]);
329 | }
330 |
331 | return translator.CanTranslate(fromLcid, destLcid)
332 | .then(function(canTranslate) {
333 | if (canTranslate[fromLcid] && canTranslate[destLcid]) {
334 | return [translator];
335 | }
336 |
337 | const errorMsg = BuildError(preFallBackError, authProvider + " translator does not support the current languages: " + fromLcid + "(" + canTranslate[fromLcid] + "), " + destLcid + "(" + canTranslate[destLcid] + ")");
338 |
339 | return [null, errorMsg];
340 | })
341 | }
342 |
343 | TranslationHandler.ProposeTranslations = function(recordsRaw, fromLcid, destLcid, translateMissing, apiProvider) {
344 | XrmTranslator.LockGrid("Translating...");
345 |
346 | var records = !translateMissing
347 | ? recordsRaw
348 | : recordsRaw.filter(function (record) {
349 | // If original record had translation set and it was not cleared by pending changes, we skip this record
350 | if (record[destLcid] && (!record.w2ui || !record.w2ui.changes || record.w2ui.changes[destLcid]) && (translateMissing !== "missingOrIdentical" || record[fromLcid] !== record[destLcid])) {
351 | return false;
352 | }
353 |
354 | return true;
355 | });
356 |
357 | var fromIso = GetLanguageIsoByLcid(fromLcid);
358 | var toIso = GetLanguageIsoByLcid(destLcid);
359 |
360 | if (!fromIso || !toIso) {
361 | XrmTranslator.UnlockGrid();
362 |
363 | w2alert("Could not find source or target language mapping, source iso:" + fromIso + ", target iso: " + toIso);
364 |
365 | return;
366 | }
367 |
368 | FindTranslator(XrmTranslator.config.translationApiKey, XrmTranslator.config.translationApiProvider, XrmTranslator.config.translationApiRegion, fromIso, toIso, apiProvider)
369 | .then(function (result) {
370 | if (!result[0] && XrmTranslator.config.translationApiProviderFallback) {
371 | return FindTranslator(XrmTranslator.config.translationApiKeyFallback, XrmTranslator.config.translationApiProviderFallback, XrmTranslator.config.translationApiRegionFallback, fromIso, toIso, apiProvider, result[1])
372 | }
373 | return result;
374 | })
375 | .then(function(result) {
376 | var translator = result[0];
377 |
378 | if (!translator) {
379 | w2alert(result[1]);
380 | return null;
381 | }
382 |
383 | var updateRecords = [];
384 | var translationRequests = [];
385 |
386 | for (var i = 0; i < records.length; i++) {
387 | var record = records[i];
388 |
389 | // Skip records that have no source text
390 | if (!record[fromLcid]) {
391 | continue;
392 | }
393 |
394 | const source = XrmTranslator.config.translationExceptions && XrmTranslator.config.translationExceptions.length
395 | ? XrmTranslator.config.translationExceptions.reduce(function(all, cur) {
396 | return (all || "").replace(new RegExp(cur, "gmi"), '')
397 | }, record[fromLcid])
398 | : record[fromLcid]
399 |
400 | updateRecords.push(record);
401 | translationRequests.push(translator.GetTranslation(fromIso, toIso, w2utils.decodeTags(source)));
402 | }
403 |
404 | return WebApiClient.Promise.all(translationRequests)
405 | .then(function (responses) {
406 | ShowTranslationResults(translator.AddTranslations(fromLcid, destLcid, updateRecords, responses));
407 | XrmTranslator.UnlockGrid();
408 | });
409 | })
410 | .catch(XrmTranslator.errorHandler);
411 | }
412 |
413 | function InitializeTranslationPrompt () {
414 | var languageItems = [];
415 | var availableLanguages = XrmTranslator.GetGrid().columns;
416 |
417 | for (var i = 0; i < availableLanguages.length; i++) {
418 | if (availableLanguages[i].field === "schemaName") {
419 | continue;
420 | }
421 |
422 | languageItems.push({ id: availableLanguages[i].field, text: availableLanguages[i].caption });
423 | }
424 |
425 | if (!w2ui.translationPrompt)
426 | {
427 | $().w2form({
428 | name: 'translationPrompt',
429 | style: 'border: 0px; background-color: transparent;',
430 | formHTML:
431 | '