widget.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. /**
  2. * Durandal 2.1.0 Copyright (c) 2012 Blue Spire Consulting, Inc. All Rights Reserved.
  3. * Available via the MIT license.
  4. * see: http://durandaljs.com or https://github.com/BlueSpire/Durandal for details.
  5. */
  6. /**
  7. * Layers the widget sugar on top of the composition system.
  8. * @module widget
  9. * @requires system
  10. * @requires composition
  11. * @requires jquery
  12. * @requires knockout
  13. */
  14. define(['durandal/system', 'durandal/composition', 'jquery', 'knockout'], function(system, composition, $, ko) {
  15. var kindModuleMaps = {},
  16. kindViewMaps = {},
  17. bindableSettings = ['model', 'view', 'kind'],
  18. widgetDataKey = 'durandal-widget-data';
  19. function extractParts(element, settings){
  20. var data = ko.utils.domData.get(element, widgetDataKey);
  21. if(!data){
  22. data = {
  23. parts:composition.cloneNodes(ko.virtualElements.childNodes(element))
  24. };
  25. ko.virtualElements.emptyNode(element);
  26. ko.utils.domData.set(element, widgetDataKey, data);
  27. }
  28. settings.parts = data.parts;
  29. }
  30. /**
  31. * @class WidgetModule
  32. * @static
  33. */
  34. var widget = {
  35. getSettings: function(valueAccessor) {
  36. var settings = ko.utils.unwrapObservable(valueAccessor()) || {};
  37. if (system.isString(settings)) {
  38. return { kind: settings };
  39. }
  40. for (var attrName in settings) {
  41. if (ko.utils.arrayIndexOf(bindableSettings, attrName) != -1) {
  42. settings[attrName] = ko.utils.unwrapObservable(settings[attrName]);
  43. } else {
  44. settings[attrName] = settings[attrName];
  45. }
  46. }
  47. return settings;
  48. },
  49. /**
  50. * Creates a ko binding handler for the specified kind.
  51. * @method registerKind
  52. * @param {string} kind The kind to create a custom binding handler for.
  53. */
  54. registerKind: function(kind) {
  55. ko.bindingHandlers[kind] = {
  56. init: function() {
  57. return { controlsDescendantBindings: true };
  58. },
  59. update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
  60. var settings = widget.getSettings(valueAccessor);
  61. settings.kind = kind;
  62. extractParts(element, settings);
  63. widget.create(element, settings, bindingContext, true);
  64. }
  65. };
  66. ko.virtualElements.allowedBindings[kind] = true;
  67. composition.composeBindings.push(kind + ':');
  68. },
  69. /**
  70. * Maps views and module to the kind identifier if a non-standard pattern is desired.
  71. * @method mapKind
  72. * @param {string} kind The kind name.
  73. * @param {string} [viewId] The unconventional view id to map the kind to.
  74. * @param {string} [moduleId] The unconventional module id to map the kind to.
  75. */
  76. mapKind: function(kind, viewId, moduleId) {
  77. if (viewId) {
  78. kindViewMaps[kind] = viewId;
  79. }
  80. if (moduleId) {
  81. kindModuleMaps[kind] = moduleId;
  82. }
  83. },
  84. /**
  85. * Maps a kind name to it's module id. First it looks up a custom mapped kind, then falls back to `convertKindToModulePath`.
  86. * @method mapKindToModuleId
  87. * @param {string} kind The kind name.
  88. * @return {string} The module id.
  89. */
  90. mapKindToModuleId: function(kind) {
  91. return kindModuleMaps[kind] || widget.convertKindToModulePath(kind);
  92. },
  93. /**
  94. * Converts a kind name to it's module path. Used to conventionally map kinds who aren't explicitly mapped through `mapKind`.
  95. * @method convertKindToModulePath
  96. * @param {string} kind The kind name.
  97. * @return {string} The module path.
  98. */
  99. convertKindToModulePath: function(kind) {
  100. return 'widgets/' + kind + '/viewmodel';
  101. },
  102. /**
  103. * Maps a kind name to it's view id. First it looks up a custom mapped kind, then falls back to `convertKindToViewPath`.
  104. * @method mapKindToViewId
  105. * @param {string} kind The kind name.
  106. * @return {string} The view id.
  107. */
  108. mapKindToViewId: function(kind) {
  109. return kindViewMaps[kind] || widget.convertKindToViewPath(kind);
  110. },
  111. /**
  112. * Converts a kind name to it's view id. Used to conventionally map kinds who aren't explicitly mapped through `mapKind`.
  113. * @method convertKindToViewPath
  114. * @param {string} kind The kind name.
  115. * @return {string} The view id.
  116. */
  117. convertKindToViewPath: function(kind) {
  118. return 'widgets/' + kind + '/view';
  119. },
  120. createCompositionSettings: function(element, settings) {
  121. if (!settings.model) {
  122. settings.model = this.mapKindToModuleId(settings.kind);
  123. }
  124. if (!settings.view) {
  125. settings.view = this.mapKindToViewId(settings.kind);
  126. }
  127. settings.preserveContext = true;
  128. settings.activate = true;
  129. settings.activationData = settings;
  130. settings.mode = 'templated';
  131. return settings;
  132. },
  133. /**
  134. * Creates a widget.
  135. * @method create
  136. * @param {DOMElement} element The DOMElement or knockout virtual element that serves as the target element for the widget.
  137. * @param {object} settings The widget settings.
  138. * @param {object} [bindingContext] The current binding context.
  139. */
  140. create: function(element, settings, bindingContext, fromBinding) {
  141. if(!fromBinding){
  142. settings = widget.getSettings(function() { return settings; }, element);
  143. }
  144. var compositionSettings = widget.createCompositionSettings(element, settings);
  145. composition.compose(element, compositionSettings, bindingContext);
  146. },
  147. /**
  148. * Installs the widget module by adding the widget binding handler and optionally registering kinds.
  149. * @method install
  150. * @param {object} config The module config. Add a `kinds` array with the names of widgets to automatically register. You can also specify a `bindingName` if you wish to use another name for the widget binding, such as "control" for example.
  151. */
  152. install:function(config){
  153. config.bindingName = config.bindingName || 'widget';
  154. if(config.kinds){
  155. var toRegister = config.kinds;
  156. for(var i = 0; i < toRegister.length; i++){
  157. widget.registerKind(toRegister[i]);
  158. }
  159. }
  160. ko.bindingHandlers[config.bindingName] = {
  161. init: function() {
  162. return { controlsDescendantBindings: true };
  163. },
  164. update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
  165. var settings = widget.getSettings(valueAccessor);
  166. extractParts(element, settings);
  167. widget.create(element, settings, bindingContext, true);
  168. }
  169. };
  170. composition.composeBindings.push(config.bindingName + ':');
  171. ko.virtualElements.allowedBindings[config.bindingName] = true;
  172. }
  173. };
  174. return widget;
  175. });