jobsub.ko.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. // Licensed to Cloudera, Inc. under one
  2. // or more contributor license agreements. See the NOTICE file
  3. // distributed with this work for additional information
  4. // regarding copyright ownership. Cloudera, Inc. licenses this file
  5. // to you under the Apache License, Version 2.0 (the
  6. // "License"); you may not use this file except in compliance
  7. // with the License. You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. /**
  17. * Design representation
  18. */
  19. var Design = (function($, ko, NodeFields) {
  20. var module = function(options) {
  21. var self = this;
  22. self.options = {};
  23. self.model = {};
  24. self.initialize(options);
  25. };
  26. // NodeFields is defined in oozie/static/js/workflow.node-fields.js.
  27. // It provides the more advanced field manipulation for the fields
  28. // that hava JSON representation.
  29. $.extend(module.prototype, NodeFields, {
  30. initialize: function(options) {
  31. var self = this;
  32. self.options = $.extend(self.options, options);
  33. self.model = options.model;
  34. self.model.errors = self.model.errors || {};
  35. for(var key in self.model) {
  36. switch(key) {
  37. case 'initialize':
  38. case 'toString':
  39. case 'errors':
  40. break;
  41. default:
  42. if (!(key in self.model.errors)) {
  43. self.model.errors[key] = [];
  44. }
  45. break;
  46. }
  47. }
  48. // @see http://knockoutjs.com/documentation/plugins-mapping.html
  49. // MAPPING_OPTIONS comes from /oozie/static/js/models.js
  50. // We don't update the observed object using ko.mapping because
  51. // the plugin does not work with mixed objects.
  52. ko.mapping.fromJS(self.model, MAPPING_OPTIONS, self);
  53. // hack on '<key>ErrorClass' and '<key>Condition'.
  54. $.each(self.__ko_mapping__, function(key, enabled) {
  55. if (ko.isObservable(self[key])) {
  56. self[key+'_condition'] = ko.computed(function() {
  57. return self[key]().length > 0;
  58. });
  59. self[key+'_error_class'] = ko.computed(function() {
  60. return ( self.errors[key]().length > 0 ) ? 'control-group error' : 'control-group';
  61. });
  62. }
  63. });
  64. if (!('is_dirty' in self)) {
  65. self.is_dirty = ko.observable(true);
  66. }
  67. if (!('new' in self)) {
  68. self.new = ko.computed(function() {
  69. return !self.id();
  70. });
  71. }
  72. if (!('editable' in self)) {
  73. self.editable = ko.observable(false);
  74. }
  75. $(document).trigger('initialize.design', [options, self]);
  76. },
  77. request: function(url, options) {
  78. var self = this;
  79. var request = $.extend({
  80. url: url,
  81. dataType: 'json',
  82. type: 'GET',
  83. success: $.noop,
  84. error: $.noop
  85. }, options || {});
  86. $.ajax(request);
  87. },
  88. load: function(options) {
  89. var self = this;
  90. var options = $.extend({
  91. success: function(data) {
  92. self.is_dirty(false);
  93. $(document).trigger('load.design', [options, data]);
  94. }
  95. }, options);
  96. this.request('/jobsub/designs/' + self.id(), options);
  97. },
  98. save: function(options) {
  99. // First try to save, then update error list if fail.
  100. // Response should be json object. IE: {data: {errors: {files: ['example', ...], ... }}}
  101. var self = this;
  102. var model_dict = {};
  103. $.each(ko.mapping.toJS(self), function(key, value) {
  104. if (key != 'errors') {
  105. model_dict[key] = ko.utils.unwrapObservable(value);
  106. }
  107. });
  108. var data = normalize_model_fields($.parseJSON(JSON.stringify(model_dict)));
  109. var options = $.extend({
  110. type: 'POST',
  111. data: data,
  112. error: function(xhr) {
  113. var response = $.parseJSON(xhr.responseText);
  114. if (response) {
  115. var model = ko.mapping.toJS(self);
  116. $.extend(model.errors, response.data.errors);
  117. ko.mapping.fromJS(model, self);
  118. $(document).trigger('error.design', [options, data]);
  119. }
  120. },
  121. success: function(data) {
  122. $(document).trigger('save.design', [options, data]);
  123. }
  124. }, options);
  125. self.request((self.new()) ? '/jobsub/designs/'+self.node_type()+'/new' : '/jobsub/designs/'+self.id()+'/save', options);
  126. },
  127. clone: function(options) {
  128. var self = this;
  129. var options = $.extend({
  130. type: 'POST',
  131. success: function(data) {
  132. $(document).trigger('clone.design', [options, data]);
  133. }
  134. }, options);
  135. this.request('/jobsub/designs/' + self.id() + '/clone', options);
  136. },
  137. delete: function(options) {
  138. var self = this;
  139. var options = $.extend({
  140. type: 'POST',
  141. success: function(data) {
  142. $(document).trigger('delete.design', [options, data]);
  143. }
  144. }, options);
  145. this.request('/jobsub/designs/' + self.id() + '/delete', options);
  146. }
  147. });
  148. return module;
  149. })($, ko, NodeFields);
  150. /**
  151. * List of designs
  152. */
  153. var Designs = (function($, ko, NodeModelChooser) {
  154. var module = function(options) {
  155. var self = this;
  156. self.options = options || {
  157. models: []
  158. };
  159. self.temporary = ko.observable();
  160. self.designs = ko.observableArray([]);
  161. self.selectedDesignObjects = ko.computed(function() {
  162. var selected = [];
  163. $.each(self.designs(), function(index, designObject) {
  164. if (designObject.selected()) {
  165. selected.push(designObject);
  166. }
  167. });
  168. return selected;
  169. });
  170. self.selectedDesignObject = ko.computed(function () {
  171. return self.selectedDesignObjects()[0];
  172. });
  173. self.selectedDesign = ko.computed(function() {
  174. if (self.selectedDesignObject()) {
  175. return self.selectedDesignObject().design();
  176. } else {
  177. return null;
  178. }
  179. });
  180. self.selectedIndex = ko.computed(function() {
  181. var selected = -1;
  182. $.each(self.designs(), function(index, designObject) {
  183. if (selected == -1 && designObject.selected()) {
  184. selected = index;
  185. }
  186. });
  187. return selected;
  188. });
  189. self.allSelected = ko.computed(function() {
  190. return self.selectedDesignObjects().length == self.designs().length;
  191. });
  192. self.initialize(options);
  193. };
  194. $.extend(module.prototype, {
  195. initialize: function(options) {
  196. var self = this;
  197. self.options = $.extend(self.options, options);
  198. self.designs.removeAll();
  199. self.createDesigns(self.options.models);
  200. self.temporary({
  201. design: ko.observable(null),
  202. selected: ko.observable(false),
  203. template: ko.observable(null)
  204. })
  205. self.deselectAll();
  206. $(document).trigger('initialize.designs', [options, self]);
  207. },
  208. load: function(options) {
  209. // Fetch designs from backend.
  210. var self = this;
  211. var request = $.extend({
  212. url: '/jobsub/designs',
  213. dataType: 'json',
  214. type: 'GET',
  215. success: function(data) {
  216. $(document).trigger('load.designs', [options, data]);
  217. self.initialize({models: data});
  218. },
  219. error: $.noop
  220. }, options || {});
  221. $.ajax(request);
  222. },
  223. ensureListFields: function(model) {
  224. if (!('name' in model)) {
  225. model.name = '';
  226. }
  227. if (!('description' in model)) {
  228. model.description = '';
  229. }
  230. if (!('owner' in model)) {
  231. model.owner = '';
  232. }
  233. if (!('last_modified' in model)) {
  234. model.last_modified = 0;
  235. }
  236. return model;
  237. },
  238. createDesign: function(model) {
  239. var self = this;
  240. var NodeModel = NodeModelChooser(model.node_type);
  241. var node_model = new NodeModel(self.ensureListFields(model));
  242. var design = new Design({model: node_model});
  243. return design;
  244. },
  245. createDesigns: function(models) {
  246. var self = this;
  247. $.each(models, function(index, model) {
  248. self.designs.push({
  249. design: ko.observable(self.createDesign(model)),
  250. selected: ko.observable(false),
  251. template: ko.observable(model.node_type)
  252. });
  253. });
  254. },
  255. toggleSelect: function(index) {
  256. var self = this;
  257. self.designs()[index].selected(!self.designs()[index].selected());
  258. },
  259. select: function(index) {
  260. var self = this;
  261. self.designs()[index].selected(true);
  262. },
  263. toggleSelectAll: function() {
  264. var self = this;
  265. if (self.allSelected()) {
  266. self.deselectAll();
  267. } else {
  268. self.selectAll();
  269. }
  270. },
  271. selectAll: function() {
  272. var self = this;
  273. $.each(self.designs(), function(index, value) {
  274. value.selected(true);
  275. });
  276. },
  277. deselectAll: function() {
  278. var self = this;
  279. $.each(self.designs(), function(index, value) {
  280. value.selected(false);
  281. });
  282. },
  283. //// Design delegation
  284. newDesign: function(node_type) {
  285. var self = this;
  286. var design = self.createDesign({
  287. id: null,
  288. node_type: node_type
  289. });
  290. // Reversing the order of the next two statements may cause KO to break.
  291. self.temporary().template(node_type);
  292. self.temporary().design(design);
  293. // Do not do any thing with any other design.
  294. self.deselectAll();
  295. $(document).trigger('new.design', [design]);
  296. },
  297. saveDesign: function(data, event) {
  298. var self = this;
  299. self.temporary().design().save();
  300. },
  301. cloneDesigns: function() {
  302. var self = this;
  303. $.each(self.selectedDesignObjects(), function(index, designObject) {
  304. designObject.design().clone();
  305. });
  306. },
  307. deleteDesigns: function() {
  308. var self = this;
  309. $.each(self.selectedDesignObjects(), function(index, designObject) {
  310. designObject.design().delete();
  311. });
  312. },
  313. editDesign: function(index) {
  314. var self = this;
  315. if (self.selectedDesignObject()) {
  316. var design = self.selectedDesignObject().design();
  317. if (design.is_dirty()) {
  318. $(document).one('load.design', function(e, options, data) {
  319. design.initialize({model: data});
  320. self.temporary().design(design);
  321. self.temporary().template(self.selectedDesignObject().template());
  322. $(document).trigger('edit.design', [design]);
  323. });
  324. design.load();
  325. } else {
  326. self.temporary().design(design);
  327. self.temporary().template(self.selectedDesignObject().template());
  328. $(document).trigger('edit.design', [design]);
  329. }
  330. }
  331. },
  332. closeDesign: function() {
  333. var self = this;
  334. self.temporary().design(null);
  335. self.temporary().template(null);
  336. }
  337. });
  338. return module;
  339. })($, ko, nodeModelChooser);
  340. var designs = new Designs({models: []});