fanforce-app-factory 1.7.1 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (206) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -1
  3. data/.yardopts +5 -0
  4. data/.yardopts.rb +1 -0
  5. data/Gemfile +10 -1
  6. data/README.md +12 -12
  7. data/Rakefile +8 -3
  8. data/apps.json +787 -0
  9. data/fanforce-app-factory.gemspec +4 -5
  10. data/lib/fanforce/app_factory.rb +15 -52
  11. data/lib/fanforce/app_factory/Rakefile +59 -0
  12. data/lib/fanforce/app_factory/app.rb +50 -0
  13. data/lib/fanforce/app_factory/asset_framework/app_factory/_bootstrap.scss +2 -0
  14. data/lib/fanforce/app_factory/asset_framework/app_factory/_common.scss +1 -0
  15. data/lib/fanforce/app_factory/asset_framework/app_factory/_directives.scss +7 -0
  16. data/lib/fanforce/app_factory/asset_framework/app_factory/_font-awesome.scss +1 -0
  17. data/lib/fanforce/app_factory/asset_framework/app_factory/_ui-select.scss +3 -0
  18. data/lib/fanforce/app_factory/{assets/lib/common → asset_framework/app_factory/bootstrap}/_bootstrap-overrides.scss +2 -0
  19. data/lib/fanforce/app_factory/{assets/lib/vendors/bootstrap/css → asset_framework/app_factory/bootstrap}/bootstrap.css +2 -2
  20. data/lib/fanforce/app_factory/{assets/lib/vendors/bootstrap/js → asset_framework/app_factory/bootstrap}/bootstrap.js +0 -0
  21. data/lib/fanforce/app_factory/asset_framework/app_factory/bootstrap/bootstrap.min.css +7 -0
  22. data/lib/fanforce/app_factory/{assets/lib/vendors/bootstrap/js → asset_framework/app_factory/bootstrap}/bootstrap.min.js +0 -0
  23. data/lib/fanforce/app_factory/{assets/lib/vendors/bootstrap/fonts → asset_framework/app_factory/bootstrap}/glyphicons-halflings-regular.eot +0 -0
  24. data/lib/fanforce/app_factory/{assets/lib/vendors/bootstrap/fonts → asset_framework/app_factory/bootstrap}/glyphicons-halflings-regular.svg +0 -0
  25. data/lib/fanforce/app_factory/{assets/lib/vendors/bootstrap/fonts → asset_framework/app_factory/bootstrap}/glyphicons-halflings-regular.ttf +0 -0
  26. data/lib/fanforce/app_factory/{assets/lib/vendors/bootstrap/fonts → asset_framework/app_factory/bootstrap}/glyphicons-halflings-regular.woff +0 -0
  27. data/lib/fanforce/app_factory/{assets/lib → asset_framework/app_factory}/common/_mixins.scss +0 -0
  28. data/lib/fanforce/app_factory/asset_framework/app_factory/directives.coffee +1 -0
  29. data/lib/fanforce/app_factory/asset_framework/app_factory/directives/error/_error.scss +6 -0
  30. data/lib/fanforce/app_factory/{assets/lib/common/module-error → asset_framework/app_factory/directives/error}/arrow-left.png +0 -0
  31. data/lib/fanforce/app_factory/{assets/lib/common/module-error → asset_framework/app_factory/directives/error}/icon-error.png +0 -0
  32. data/lib/fanforce/app_factory/asset_framework/app_factory/directives/initiative-footer/_initiative-footer.coffee +16 -0
  33. data/lib/fanforce/app_factory/{assets/lib/common/_module-add-initiative-footer.scss → asset_framework/app_factory/directives/initiative-footer/_initiative-footer.scss} +4 -1
  34. data/lib/fanforce/app_factory/{assets/lib/common/_module-initiative-searcher.scss → asset_framework/app_factory/directives/initiative-searcher/_initiative-searcher.scss} +6 -6
  35. data/lib/fanforce/app_factory/{assets/lib/common/module-initiative-searcher → asset_framework/app_factory/directives/initiative-searcher}/icon-search.png +0 -0
  36. data/lib/fanforce/app_factory/{assets/lib/common/module-initiative-searcher → asset_framework/app_factory/directives/initiative-searcher}/spinner.gif +0 -0
  37. data/lib/fanforce/app_factory/{assets/lib/common/module-initiative-searcher → asset_framework/app_factory/directives/initiative-searcher}/sprite-arrows.png +0 -0
  38. data/lib/fanforce/app_factory/asset_framework/app_factory/directives/loading/_loading.coffee +4 -0
  39. data/lib/fanforce/app_factory/asset_framework/app_factory/directives/loading/_loading.scss +4 -0
  40. data/lib/fanforce/app_factory/{assets/js/add_source_popup.js → asset_framework/app_factory/directives/saving-to-server/_saving-to-server.coffee} +0 -0
  41. data/lib/fanforce/app_factory/asset_framework/app_factory/directives/saving-to-server/_saving-to-server.scss +6 -0
  42. data/lib/fanforce/app_factory/{assets/lib/common/module-saving-initiative → asset_framework/app_factory/directives/saving-to-server}/spinner.gif +0 -0
  43. data/lib/fanforce/app_factory/asset_framework/app_factory/directives/start-arrow/_start-arrow.coffee +8 -0
  44. data/lib/fanforce/app_factory/{assets/lib/common/_module-start-here.scss → asset_framework/app_factory/directives/start-arrow/_start-arrow.scss} +5 -4
  45. data/lib/fanforce/app_factory/{assets/lib/common/module-start-here → asset_framework/app_factory/directives/start-arrow}/arrow.png +0 -0
  46. data/lib/fanforce/app_factory/{assets/lib/common/_module-video-thumbnail.scss → asset_framework/app_factory/directives/video-thumbnail/_video-thumbnail.scss} +3 -3
  47. data/lib/fanforce/app_factory/{assets/lib/common/module-video-thumbnail → asset_framework/app_factory/directives/video-thumbnail}/icon-play.png +0 -0
  48. data/lib/fanforce/app_factory/asset_framework/app_factory/font-awesome.coffee +1 -0
  49. data/lib/fanforce/app_factory/{assets/lib/vendors/font-awesome/font → asset_framework/app_factory/font-awesome}/FontAwesome.otf +0 -0
  50. data/lib/fanforce/app_factory/{assets/lib/vendors/font-awesome/css → asset_framework/app_factory/font-awesome}/font-awesome.css +2 -2
  51. data/lib/fanforce/app_factory/{assets/lib/vendors/font-awesome/css → asset_framework/app_factory/font-awesome}/font-awesome.min.css +0 -0
  52. data/lib/fanforce/app_factory/{assets/lib/vendors/font-awesome/font → asset_framework/app_factory/font-awesome}/fontawesome-webfont.eot +0 -0
  53. data/lib/fanforce/app_factory/{assets/lib/vendors/font-awesome/font → asset_framework/app_factory/font-awesome}/fontawesome-webfont.svg +0 -0
  54. data/lib/fanforce/app_factory/{assets/lib/vendors/font-awesome/font → asset_framework/app_factory/font-awesome}/fontawesome-webfont.ttf +0 -0
  55. data/lib/fanforce/app_factory/{assets/lib/vendors/font-awesome/font → asset_framework/app_factory/font-awesome}/fontawesome-webfont.woff +0 -0
  56. data/lib/fanforce/app_factory/asset_framework/app_factory/lib.coffee +1 -0
  57. data/lib/fanforce/app_factory/asset_framework/app_factory/lib/cookie.coffee +16 -0
  58. data/lib/fanforce/app_factory/asset_framework/app_factory/lib/ng-focus-blur.js +68 -0
  59. data/lib/fanforce/app_factory/asset_framework/app_factory/lib/ng-visible.js +7 -0
  60. data/lib/fanforce/app_factory/asset_framework/app_factory/lib/url.coffee +20 -0
  61. data/lib/fanforce/app_factory/asset_framework/app_factory/lib/utils.coffee +11 -0
  62. data/lib/fanforce/app_factory/asset_framework/app_factory/scafolding/_promotional.scss +1 -0
  63. data/lib/fanforce/app_factory/asset_framework/app_factory/scafolding/_standard.scss +1 -0
  64. data/lib/fanforce/app_factory/{assets/js/fanforce-engager.js → asset_framework/app_factory/scafolding/promotional.coffee} +0 -0
  65. data/lib/fanforce/app_factory/asset_framework/app_factory/ui-select.coffee +1 -0
  66. data/lib/fanforce/app_factory/asset_framework/app_factory/ui-select/select.css +171 -0
  67. data/lib/fanforce/app_factory/asset_framework/app_factory/ui-select/select.js +1281 -0
  68. data/lib/fanforce/app_factory/asset_framework/app_factory/ui-select/select_override.scss +1 -0
  69. data/lib/fanforce/app_factory/asset_framework/app_factory/ui-select/selectize.default.css +443 -0
  70. data/lib/fanforce/app_factory/cli/_cleanorgs.rb +54 -0
  71. data/lib/fanforce/app_factory/controllers/base_controller.rb +5 -1
  72. data/lib/fanforce/app_factory/core_config.rb +95 -0
  73. data/lib/fanforce/app_factory/scafolding/Routes.rb +64 -0
  74. data/lib/fanforce/app_factory/{layouts → scafolding/layouts}/engage.haml +0 -0
  75. data/lib/fanforce/app_factory/{public → scafolding/public}/favicon.ico +0 -0
  76. data/lib/fanforce/app_factory/{public → scafolding/public}/robots.txt +0 -0
  77. data/lib/fanforce/app_factory/{assets/lib/common/layouts/_promotional.scss → scafolding/scafolding_assets/css/promotional.scss} +4 -9
  78. data/lib/fanforce/app_factory/scafolding/scafolding_assets/css/standard.scss +10 -0
  79. data/lib/fanforce/app_factory/{assets → scafolding/scafolding_assets}/img/icon-16.png +0 -0
  80. data/lib/fanforce/app_factory/{assets → scafolding/scafolding_assets}/img/icon-32.png +0 -0
  81. data/lib/fanforce/app_factory/{assets → scafolding/scafolding_assets}/img/icon-42.png +0 -0
  82. data/lib/fanforce/app_factory/{assets → scafolding/scafolding_assets}/img/marketplace.png +0 -0
  83. data/lib/fanforce/app_factory/{assets → scafolding/scafolding_assets}/img/marketplace.psd +0 -0
  84. data/lib/fanforce/app_factory/{assets/lib → scafolding/scafolding_assets/img}/promotional/fanforce_bg.png +0 -0
  85. data/lib/fanforce/app_factory/{assets/lib → scafolding/scafolding_assets/img}/promotional/icon-42.png +0 -0
  86. data/lib/fanforce/app_factory/{assets/lib → scafolding/scafolding_assets/img}/promotional/icon-plus.png +0 -0
  87. data/lib/fanforce/app_factory/{views → scafolding/views}/add_initiative.haml +0 -0
  88. data/lib/fanforce/app_factory/{views → scafolding/views}/add_source.haml +0 -0
  89. data/lib/fanforce/app_factory/{views → scafolding/views}/close_popup.haml +0 -0
  90. data/lib/fanforce/app_factory/{views → scafolding/views}/config.haml +0 -0
  91. data/lib/fanforce/app_factory/{views → scafolding/views}/convert_initiative.haml +0 -0
  92. data/lib/fanforce/app_factory/{views → scafolding/views}/dashboard.haml +1 -1
  93. data/lib/fanforce/app_factory/{views → scafolding/views}/edit_initiative.haml +0 -0
  94. data/lib/fanforce/app_factory/{views → scafolding/views}/engage.haml +0 -0
  95. data/lib/fanforce/app_factory/{views → scafolding/views}/index.haml +1 -1
  96. data/lib/fanforce/app_factory/{views → scafolding/views}/new_message.haml +0 -0
  97. data/lib/fanforce/app_factory/{views → scafolding/views}/source_details.haml +0 -0
  98. data/lib/fanforce/app_factory/{views → scafolding/views}/widget_templates.haml +0 -0
  99. data/lib/fanforce/app_factory/sinatra/Routes.rb +17 -0
  100. data/lib/fanforce/app_factory/sinatra/_load.rb +76 -0
  101. data/lib/fanforce/app_factory/sinatra/error_handling.rb +47 -0
  102. data/lib/fanforce/app_factory/{routes_core.rb → sinatra/helper_routes/app.rb} +8 -8
  103. data/lib/fanforce/app_factory/sinatra/helper_routes/com_behavior.rb +23 -0
  104. data/lib/fanforce/app_factory/sinatra/helper_routes/com_broadcaster.rb +7 -0
  105. data/lib/fanforce/app_factory/sinatra/helper_routes/com_connector.rb +11 -0
  106. data/lib/fanforce/app_factory/sinatra/helper_routes/com_identifier.rb +3 -0
  107. data/lib/fanforce/app_factory/{routes_js_widget.rb → sinatra/helper_routes/com_js_widget.rb} +1 -1
  108. data/lib/fanforce/app_factory/{config → sinatra}/helpers/assets.rb +1 -1
  109. data/lib/fanforce/app_factory/sinatra/helpers/fanforce.rb +18 -0
  110. data/lib/fanforce/app_factory/{config → sinatra}/helpers/json.rb +1 -1
  111. data/lib/fanforce/app_factory/{config → sinatra}/helpers/ractive.rb +1 -1
  112. data/lib/fanforce/app_factory/sprockets/_all.rb +2 -0
  113. data/lib/fanforce/app_factory/sprockets/compiler.rb +2 -11
  114. data/lib/fanforce/app_factory/sprockets/hacks.rb +1 -1
  115. data/lib/fanforce/app_factory/version.rb +2 -2
  116. data/workers.json +323 -0
  117. metadata +121 -222
  118. data/.rbenv-gemsets +0 -1
  119. data/lib/fanforce/app_factory.rake +0 -56
  120. data/lib/fanforce/app_factory/_init_sinatra.rb +0 -80
  121. data/lib/fanforce/app_factory/assets/css/add_edit_initiative.scss +0 -1
  122. data/lib/fanforce/app_factory/assets/css/add_source.scss +0 -1
  123. data/lib/fanforce/app_factory/assets/css/config.scss +0 -1
  124. data/lib/fanforce/app_factory/assets/css/convert_initiative.scss +0 -1
  125. data/lib/fanforce/app_factory/assets/css/dashboard.scss +0 -24
  126. data/lib/fanforce/app_factory/assets/css/engage.scss +0 -1
  127. data/lib/fanforce/app_factory/assets/css/new_message.scss +0 -1
  128. data/lib/fanforce/app_factory/assets/css/promotional.scss +0 -1
  129. data/lib/fanforce/app_factory/assets/css/source_details.scss +0 -1
  130. data/lib/fanforce/app_factory/assets/js/add_edit_initiative.js +0 -1
  131. data/lib/fanforce/app_factory/assets/js/add_source.js +0 -1
  132. data/lib/fanforce/app_factory/assets/js/config.js +0 -1
  133. data/lib/fanforce/app_factory/assets/js/convert_initiative.js +0 -1
  134. data/lib/fanforce/app_factory/assets/js/dashboard.coffee +0 -18
  135. data/lib/fanforce/app_factory/assets/js/fanforce-finder.js +0 -0
  136. data/lib/fanforce/app_factory/assets/js/new_message.js +0 -1
  137. data/lib/fanforce/app_factory/assets/js/promotional.js +0 -1
  138. data/lib/fanforce/app_factory/assets/js/source_details.js +0 -0
  139. data/lib/fanforce/app_factory/assets/lib/common/_module-error.scss +0 -11
  140. data/lib/fanforce/app_factory/assets/lib/common/_module-page-loading.scss +0 -1
  141. data/lib/fanforce/app_factory/assets/lib/common/_module-saving-initiative.scss +0 -6
  142. data/lib/fanforce/app_factory/assets/lib/common/_tags.scss +0 -1
  143. data/lib/fanforce/app_factory/assets/lib/common/_variables.scss +0 -27
  144. data/lib/fanforce/app_factory/assets/lib/common/broadcaster-divider-arrow.png +0 -0
  145. data/lib/fanforce/app_factory/assets/lib/common/icons/dropdown-arrow.png +0 -0
  146. data/lib/fanforce/app_factory/assets/lib/common/icons/initiative.png +0 -0
  147. data/lib/fanforce/app_factory/assets/lib/common/layouts/_add_edit_initiative.scss +0 -27
  148. data/lib/fanforce/app_factory/assets/lib/common/layouts/_add_source.scss +0 -18
  149. data/lib/fanforce/app_factory/assets/lib/common/layouts/_config.scss +0 -27
  150. data/lib/fanforce/app_factory/assets/lib/common/layouts/_convert_initiative.scss +0 -27
  151. data/lib/fanforce/app_factory/assets/lib/common/layouts/_dashboard.scss +0 -18
  152. data/lib/fanforce/app_factory/assets/lib/common/layouts/_engage.scss +0 -80
  153. data/lib/fanforce/app_factory/assets/lib/common/layouts/_new_message.scss +0 -24
  154. data/lib/fanforce/app_factory/assets/lib/common/layouts/_source_details.scss +0 -17
  155. data/lib/fanforce/app_factory/assets/lib/common/layouts/add_edit_initiative.js +0 -6
  156. data/lib/fanforce/app_factory/assets/lib/common/layouts/add_source.js +0 -6
  157. data/lib/fanforce/app_factory/assets/lib/common/layouts/config.js +0 -6
  158. data/lib/fanforce/app_factory/assets/lib/common/layouts/convert_initiative.js +0 -6
  159. data/lib/fanforce/app_factory/assets/lib/common/layouts/dashboard.js +0 -6
  160. data/lib/fanforce/app_factory/assets/lib/common/layouts/engage.js +0 -0
  161. data/lib/fanforce/app_factory/assets/lib/common/layouts/new_message.js +0 -5
  162. data/lib/fanforce/app_factory/assets/lib/common/layouts/promotional.js +0 -1
  163. data/lib/fanforce/app_factory/assets/lib/common/layouts/source_details.js +0 -5
  164. data/lib/fanforce/app_factory/assets/lib/vendors/bootstrap-datepicker/datepicker.css +0 -7
  165. data/lib/fanforce/app_factory/assets/lib/vendors/bootstrap-datepicker/datepicker.js +0 -454
  166. data/lib/fanforce/app_factory/assets/lib/vendors/bootstrap-timepicker/timepicker.css +0 -82
  167. data/lib/fanforce/app_factory/assets/lib/vendors/bootstrap-timepicker/timepicker.js +0 -803
  168. data/lib/fanforce/app_factory/assets/lib/vendors/bootstrap/css/bootstrap.min.css +0 -7
  169. data/lib/fanforce/app_factory/assets/lib/vendors/bootstrap/img/glyphicons-halflings-regular.eot +0 -0
  170. data/lib/fanforce/app_factory/assets/lib/vendors/bootstrap/img/glyphicons-halflings-regular.svg +0 -0
  171. data/lib/fanforce/app_factory/assets/lib/vendors/bootstrap/img/glyphicons-halflings-regular.ttf +0 -0
  172. data/lib/fanforce/app_factory/assets/lib/vendors/bootstrap/img/glyphicons-halflings-regular.woff +0 -0
  173. data/lib/fanforce/app_factory/assets/lib/vendors/chosen/_chosen_override.scss +0 -6
  174. data/lib/fanforce/app_factory/assets/lib/vendors/chosen/chosen-sprite.png +0 -0
  175. data/lib/fanforce/app_factory/assets/lib/vendors/chosen/chosen-sprite@2x.png +0 -0
  176. data/lib/fanforce/app_factory/assets/lib/vendors/chosen/chosen.jquery.js +0 -1166
  177. data/lib/fanforce/app_factory/assets/lib/vendors/chosen/chosen.scss +0 -434
  178. data/lib/fanforce/app_factory/assets/lib/vendors/jquery/jquery-1.10.2.js +0 -9789
  179. data/lib/fanforce/app_factory/assets/lib/vendors/jquery/jquery.inputHint.js +0 -154
  180. data/lib/fanforce/app_factory/assets/lib/vendors/jquery/jquery.tmpl.debug.js +0 -484
  181. data/lib/fanforce/app_factory/assets/lib/vendors/knockout/knockout.custom-handlers.coffee +0 -155
  182. data/lib/fanforce/app_factory/assets/lib/vendors/knockout/knockout.debug.js +0 -4469
  183. data/lib/fanforce/app_factory/assets/lib/vendors/knockout/knockout.min.js +0 -96
  184. data/lib/fanforce/app_factory/assets/lib/vendors/select2/_select2_override.scss +0 -47
  185. data/lib/fanforce/app_factory/assets/lib/vendors/select2/select2-spinner.gif +0 -0
  186. data/lib/fanforce/app_factory/assets/lib/vendors/select2/select2.png +0 -0
  187. data/lib/fanforce/app_factory/assets/lib/vendors/select2/select2.scss +0 -617
  188. data/lib/fanforce/app_factory/assets/lib/vendors/select2/select2_modified.js +0 -3225
  189. data/lib/fanforce/app_factory/assets/lib/vendors/select2/select2x2.png +0 -0
  190. data/lib/fanforce/app_factory/assets/lib/vendors/underscore/underscore.debug.js +0 -999
  191. data/lib/fanforce/app_factory/config/_error_handling.rb +0 -27
  192. data/lib/fanforce/app_factory/config/core_config.rb +0 -104
  193. data/lib/fanforce/app_factory/config/helpers/fanforce.rb +0 -22
  194. data/lib/fanforce/app_factory/layouts/add_edit_initiative.haml +0 -18
  195. data/lib/fanforce/app_factory/layouts/add_source.haml +0 -18
  196. data/lib/fanforce/app_factory/layouts/config.haml +0 -18
  197. data/lib/fanforce/app_factory/layouts/convert_initiative.haml +0 -18
  198. data/lib/fanforce/app_factory/layouts/dashboard.haml +0 -18
  199. data/lib/fanforce/app_factory/layouts/new_message.haml +0 -18
  200. data/lib/fanforce/app_factory/layouts/promotional.haml +0 -18
  201. data/lib/fanforce/app_factory/layouts/source_details.haml +0 -18
  202. data/lib/fanforce/app_factory/routes_behavior.rb +0 -31
  203. data/lib/fanforce/app_factory/routes_broadcaster.rb +0 -7
  204. data/lib/fanforce/app_factory/routes_connector.rb +0 -11
  205. data/lib/fanforce/app_factory/routes_identifier.rb +0 -3
  206. data/lib/fanforce/app_factory_workers.rb +0 -2
@@ -0,0 +1 @@
1
+ #=require_tree ./lib
@@ -0,0 +1,16 @@
1
+ class @Cookie
2
+ @set: (name, value, exdays=null)->
3
+ exdate = new Date()
4
+ exdate.setDate(exdate.getDate() + exdays)
5
+ value = "#{escape(value)}; path=/"
6
+ value += "; expires=#{exdate.toUTCString()}" if exdays
7
+ value += "; domain=.#{BASE_DOMAIN}" if BASE_DOMAIN and URL.parse(window.location.href).root_domain != '0.0'
8
+ document.cookie = "#{name}=#{value}"
9
+
10
+ @get: (name)->
11
+ cookies = document.cookie.split(";")
12
+ for cookie in cookies
13
+ x = cookie.substr(0,cookie.indexOf("="))
14
+ y = cookie.substr(cookie.indexOf("=")+1)
15
+ x = x.replace(/^\s+|\s+$/g,"")
16
+ return unescape(y) if (x==name)
@@ -0,0 +1,68 @@
1
+ App.directive('ngFocus',function($parse,$timeout){
2
+ return function(scope,element,attrs){
3
+ var ngFocusGet = $parse(attrs.ngFocus);
4
+ var ngFocusSet = ngFocusGet.assign;
5
+ if (!ngFocusSet) {
6
+ throw Error("Non assignable expression");
7
+ }
8
+
9
+ var digesting = false;
10
+
11
+ var abortFocusing = false;
12
+ var unwatch = scope.$watch(attrs.ngFocus,function(newVal){
13
+ if(newVal){
14
+ $timeout(function(){
15
+ element[0].focus();
16
+ },0)
17
+ }
18
+ else {
19
+ $timeout(function(){
20
+ element[0].blur();
21
+ },0);
22
+ }
23
+ });
24
+
25
+
26
+ element.bind("blur",function(){
27
+
28
+ if(abortFocusing) return;
29
+
30
+ $timeout(function(){
31
+ ngFocusSet(scope,false);
32
+ },0);
33
+
34
+ });
35
+
36
+
37
+ var timerStarted = false;
38
+ var focusCount = 0;
39
+
40
+ function startTimer(){
41
+ $timeout(function(){
42
+ timerStarted = false;
43
+ if(focusCount > 3){
44
+ unwatch();
45
+ abortFocusing = true;
46
+ throw new Error("Aborting : ngFocus cannot be assigned to the same variable with multiple elements");
47
+ }
48
+ },200);
49
+ }
50
+
51
+ element.bind("focus",function(){
52
+
53
+ if(abortFocusing) return;
54
+
55
+ if(!timerStarted){
56
+ timerStarted = true;
57
+ focusCount = 0;
58
+ startTimer();
59
+ }
60
+ focusCount++;
61
+
62
+ $timeout(function(){
63
+ ngFocusSet(scope,true);
64
+ },0);
65
+
66
+ });
67
+ };
68
+ });
@@ -0,0 +1,7 @@
1
+ App.directive('ngVisible', function() {
2
+ return function(scope, element, attr) {
3
+ scope.$watch(attr.ngVisible, function(visible) {
4
+ element.css('display', visible ? '' : 'none');
5
+ });
6
+ };
7
+ });
@@ -0,0 +1,20 @@
1
+ @URL =
2
+ parse: (url_string) ->
3
+ uriPartNames = ["source","scheme","authority","host","port","path","directory_path","file_name","query_string","hash"]
4
+ uriParts = new RegExp("^(?:([^:/?#.]+):)?(?://)?(([^:/?#]*)(?::(\\d*))?)?((/(?:[^?#](?![^?#/]*\\.[^?#/.]+(?:[\\?#]|$)))*/?)?([^?#/]*))?(?:\\?([^#]*))?(?:#(.*))?").exec(url_string)
5
+ uri = {};
6
+ for i in [0..uriPartNames.length]
7
+ uri[uriPartNames[i]] = (if uriParts[i] then uriParts[i] else '')
8
+ uri.port = if uri.port then parseInt(uri.port) else 80
9
+ domain_parts = (/^(.*?)\.?([^\.]*\.\w+)$/).exec(uri.host)
10
+ if domain_parts?
11
+ uri.sub_domain = domain_parts[1]
12
+ uri.root_domain = domain_parts[2]
13
+ else
14
+ uri.sub_domain = uri.root_domain = ''
15
+ if(uri.directory_path.length > 0)
16
+ uri.directory_path = uri.directory_path.replace(/\/?$/, "/")
17
+ return uri
18
+
19
+ parseQueryString: (query_string) ->
20
+ $.deparam(query_string)
@@ -0,0 +1,11 @@
1
+ window.Utils =
2
+ $safeApply: (fn) ->
3
+ phase = this.$root.$$phase;
4
+ if(phase == '$apply' || phase == '$digest')
5
+ fn() if(fn && (typeof(fn) == 'function'))
6
+ else
7
+ this.$apply(fn)
8
+
9
+ removeFromArray: (value, array) ->
10
+ index = array.indexOf(value)
11
+ array.splice(index, 1)
@@ -0,0 +1 @@
1
+ @import 'scafolding_assets/css/promotional';
@@ -0,0 +1 @@
1
+ @import 'scafolding_assets/css/promotional';
@@ -0,0 +1 @@
1
+ #=require_tree ./ui-select
@@ -0,0 +1,171 @@
1
+ /*!
2
+ * ui-select
3
+ * http://github.com/angular-ui/ui-select
4
+ * Version: 0.8.3 - 2014-11-25T22:50:54.099Z
5
+ * License: MIT
6
+ */
7
+
8
+
9
+ /* Style when highlighting a search. */
10
+ .ui-select-highlight {
11
+ font-weight: bold;
12
+ }
13
+
14
+ .ui-select-offscreen {
15
+ clip: rect(0 0 0 0) !important;
16
+ width: 1px !important;
17
+ height: 1px !important;
18
+ border: 0 !important;
19
+ margin: 0 !important;
20
+ padding: 0 !important;
21
+ overflow: hidden !important;
22
+ position: absolute !important;
23
+ outline: 0 !important;
24
+ left: 0px !important;
25
+ top: 0px !important;
26
+ }
27
+
28
+ /* Select2 theme */
29
+
30
+ /* Mark invalid Select2 */
31
+ .ng-dirty.ng-invalid > a.select2-choice {
32
+ border-color: #D44950;
33
+ }
34
+
35
+ .select2-result-single {
36
+ padding-left: 0;
37
+ }
38
+
39
+ .select2-locked > .select2-search-choice-close{
40
+ display:none;
41
+ }
42
+
43
+ /* Selectize theme */
44
+
45
+ /* Helper class to show styles when focus */
46
+ .selectize-input.selectize-focus{
47
+ border-color: #007FBB !important;
48
+ }
49
+
50
+ /* Fix input width for Selectize theme */
51
+ .selectize-control > .selectize-input > input {
52
+ width: 100%;
53
+ }
54
+
55
+ /* Fix dropdown width for Selectize theme */
56
+ .selectize-control > .selectize-dropdown {
57
+ width: 100%;
58
+ }
59
+
60
+ /* Mark invalid Selectize */
61
+ .ng-dirty.ng-invalid > div.selectize-input {
62
+ border-color: #D44950;
63
+ }
64
+
65
+
66
+ /* Bootstrap theme */
67
+
68
+ /* Helper class to show styles when focus */
69
+ .btn-default-focus {
70
+ color: #333;
71
+ background-color: #EBEBEB;
72
+ border-color: #ADADAD;
73
+ text-decoration: none;
74
+ outline: 5px auto -webkit-focus-ring-color;
75
+ outline-offset: -2px;
76
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
77
+ }
78
+
79
+
80
+ /* Fix Bootstrap dropdown position when inside a input-group */
81
+ .input-group > .ui-select-bootstrap.dropdown {
82
+ /* Instead of relative */
83
+ position: static;
84
+ }
85
+
86
+ .input-group > .ui-select-bootstrap > input.ui-select-search.form-control {
87
+ border-radius: 4px; /* FIXME hardcoded value :-/ */
88
+ border-top-right-radius: 0;
89
+ border-bottom-right-radius: 0;
90
+ }
91
+
92
+ .ui-select-bootstrap > .ui-select-match {
93
+ /* Instead of center because of .btn */
94
+ text-align: left;
95
+ }
96
+
97
+ .ui-select-bootstrap > .ui-select-match > .caret {
98
+ position: absolute;
99
+ top: 45%;
100
+ right: 15px;
101
+ }
102
+
103
+ /* See Scrollable Menu with Bootstrap 3 http://stackoverflow.com/questions/19227496 */
104
+ .ui-select-bootstrap > .ui-select-choices {
105
+ width: 100%;
106
+ height: auto;
107
+ max-height: 200px;
108
+ overflow-x: hidden;
109
+ }
110
+
111
+ .ui-select-multiple.ui-select-bootstrap {
112
+ height: auto;
113
+ padding: .3em;
114
+ }
115
+
116
+ .ui-select-multiple.ui-select-bootstrap input.ui-select-search {
117
+ background-color: transparent !important; /* To prevent double background when disabled */
118
+ border: none;
119
+ outline: none;
120
+ height: 1.666666em;
121
+ }
122
+
123
+ .ui-select-multiple.ui-select-bootstrap .ui-select-match .close {
124
+ font-size: 1.6em;
125
+ line-height: 0.75;
126
+ }
127
+
128
+ .ui-select-multiple.ui-select-bootstrap .ui-select-match-item{
129
+ outline: 0;
130
+ }
131
+
132
+ .ui-select-bootstrap .ui-select-choices-row>a {
133
+ display: block;
134
+ padding: 3px 20px;
135
+ clear: both;
136
+ font-weight: 400;
137
+ line-height: 1.42857143;
138
+ color: #333;
139
+ white-space: nowrap;
140
+ }
141
+
142
+ .ui-select-bootstrap .ui-select-choices-row>a:hover, .ui-select-bootstrap .ui-select-choices-row>a:focus {
143
+ text-decoration: none;
144
+ color: #262626;
145
+ background-color: #f5f5f5;
146
+ }
147
+
148
+ .ui-select-bootstrap .ui-select-choices-row.active>a {
149
+ color: #fff;
150
+ text-decoration: none;
151
+ outline: 0;
152
+ background-color: #428bca;
153
+ }
154
+
155
+ .ui-select-bootstrap .ui-select-choices-row.disabled>a,
156
+ .ui-select-bootstrap .ui-select-choices-row.active.disabled>a {
157
+ color: #777;
158
+ cursor: not-allowed;
159
+ background-color: #fff;
160
+ }
161
+
162
+ /* fix hide/show angular animation */
163
+ .ui-select-match.ng-hide-add,
164
+ .ui-select-search.ng-hide-add {
165
+ display: none !important;
166
+ }
167
+
168
+ /* Mark invalid Bootstrap */
169
+ .ui-select-bootstrap.ng-dirty.ng-invalid > button.btn.ui-select-match {
170
+ border-color: #D44950;
171
+ }
@@ -0,0 +1,1281 @@
1
+ /*!
2
+ * ui-select
3
+ * http://github.com/angular-ui/ui-select
4
+ * Version: 0.8.3 - 2014-11-30T19:28:47.691Z
5
+ * License: MIT
6
+ */
7
+
8
+
9
+ (function () {
10
+ "use strict";
11
+
12
+ var KEY = {
13
+ TAB: 9,
14
+ ENTER: 13,
15
+ ESC: 27,
16
+ SPACE: 32,
17
+ LEFT: 37,
18
+ UP: 38,
19
+ RIGHT: 39,
20
+ DOWN: 40,
21
+ SHIFT: 16,
22
+ CTRL: 17,
23
+ ALT: 18,
24
+ PAGE_UP: 33,
25
+ PAGE_DOWN: 34,
26
+ HOME: 36,
27
+ END: 35,
28
+ BACKSPACE: 8,
29
+ DELETE: 46,
30
+ COMMAND: 91,
31
+
32
+ MAP: { 91 : "COMMAND", 8 : "BACKSPACE" , 9 : "TAB" , 13 : "ENTER" , 16 : "SHIFT" , 17 : "CTRL" , 18 : "ALT" , 19 : "PAUSEBREAK" , 20 : "CAPSLOCK" , 27 : "ESC" , 32 : "SPACE" , 33 : "PAGE_UP", 34 : "PAGE_DOWN" , 35 : "END" , 36 : "HOME" , 37 : "LEFT" , 38 : "UP" , 39 : "RIGHT" , 40 : "DOWN" , 43 : "+" , 44 : "PRINTSCREEN" , 45 : "INSERT" , 46 : "DELETE", 48 : "0" , 49 : "1" , 50 : "2" , 51 : "3" , 52 : "4" , 53 : "5" , 54 : "6" , 55 : "7" , 56 : "8" , 57 : "9" , 59 : ";", 61 : "=" , 65 : "A" , 66 : "B" , 67 : "C" , 68 : "D" , 69 : "E" , 70 : "F" , 71 : "G" , 72 : "H" , 73 : "I" , 74 : "J" , 75 : "K" , 76 : "L", 77 : "M" , 78 : "N" , 79 : "O" , 80 : "P" , 81 : "Q" , 82 : "R" , 83 : "S" , 84 : "T" , 85 : "U" , 86 : "V" , 87 : "W" , 88 : "X" , 89 : "Y" , 90 : "Z", 96 : "0" , 97 : "1" , 98 : "2" , 99 : "3" , 100 : "4" , 101 : "5" , 102 : "6" , 103 : "7" , 104 : "8" , 105 : "9", 106 : "*" , 107 : "+" , 109 : "-" , 110 : "." , 111 : "/", 112 : "F1" , 113 : "F2" , 114 : "F3" , 115 : "F4" , 116 : "F5" , 117 : "F6" , 118 : "F7" , 119 : "F8" , 120 : "F9" , 121 : "F10" , 122 : "F11" , 123 : "F12", 144 : "NUMLOCK" , 145 : "SCROLLLOCK" , 186 : ";" , 187 : "=" , 188 : "SPACE" , 189 : "-" , 190 : "." , 191 : "/" , 192 : "`" , 219 : "[" , 220 : "\\" , 221 : "]" , 222 : "'"
33
+ },
34
+
35
+ isControl: function (e) {
36
+ var k = e.which;
37
+ switch (k) {
38
+ case KEY.COMMAND:
39
+ case KEY.SHIFT:
40
+ case KEY.CTRL:
41
+ case KEY.ALT:
42
+ return true;
43
+ }
44
+
45
+ if (e.metaKey) return true;
46
+
47
+ return false;
48
+ },
49
+ isFunctionKey: function (k) {
50
+ k = k.which ? k.which : k;
51
+ return k >= 112 && k <= 123;
52
+ },
53
+ isVerticalMovement: function (k){
54
+ return ~[KEY.UP, KEY.DOWN].indexOf(k);
55
+ },
56
+ isHorizontalMovement: function (k){
57
+ return ~[KEY.LEFT,KEY.RIGHT,KEY.BACKSPACE,KEY.DELETE].indexOf(k);
58
+ }
59
+ };
60
+
61
+ /**
62
+ * Add querySelectorAll() to jqLite.
63
+ *
64
+ * jqLite find() is limited to lookups by tag name.
65
+ * TODO This will change with future versions of AngularJS, to be removed when this happens
66
+ *
67
+ * See jqLite.find - why not use querySelectorAll? https://github.com/angular/angular.js/issues/3586
68
+ * See feat(jqLite): use querySelectorAll instead of getElementsByTagName in jqLite.find https://github.com/angular/angular.js/pull/3598
69
+ */
70
+ if (angular.element.prototype.querySelectorAll === undefined) {
71
+ angular.element.prototype.querySelectorAll = function(selector) {
72
+ return angular.element(this[0].querySelectorAll(selector));
73
+ };
74
+ }
75
+
76
+ angular.module('ui.select', [])
77
+
78
+ .constant('uiSelectConfig', {
79
+ theme: 'bootstrap',
80
+ searchEnabled: true,
81
+ placeholder: '', // Empty by default, like HTML tag <select>
82
+ refreshDelay: 1000, // In milliseconds
83
+ closeOnSelect: true
84
+ })
85
+
86
+ // See Rename minErr and make it accessible from outside https://github.com/angular/angular.js/issues/6913
87
+ .service('uiSelectMinErr', function() {
88
+ var minErr = angular.$$minErr('ui.select');
89
+ return function() {
90
+ var error = minErr.apply(this, arguments);
91
+ var message = error.message.replace(new RegExp('\nhttp://errors.angularjs.org/.*'), '');
92
+ return new Error(message);
93
+ };
94
+ })
95
+
96
+ /**
97
+ * Parses "repeat" attribute.
98
+ *
99
+ * Taken from AngularJS ngRepeat source code
100
+ * See https://github.com/angular/angular.js/blob/v1.2.15/src/ng/directive/ngRepeat.js#L211
101
+ *
102
+ * Original discussion about parsing "repeat" attribute instead of fully relying on ng-repeat:
103
+ * https://github.com/angular-ui/ui-select/commit/5dd63ad#commitcomment-5504697
104
+ */
105
+ .service('RepeatParser', ['uiSelectMinErr','$parse', function(uiSelectMinErr, $parse) {
106
+ var self = this;
107
+
108
+ /**
109
+ * Example:
110
+ * expression = "address in addresses | filter: {street: $select.search} track by $index"
111
+ * itemName = "address",
112
+ * source = "addresses | filter: {street: $select.search}",
113
+ * trackByExp = "$index",
114
+ */
115
+ self.parse = function(expression) {
116
+
117
+ var match = expression.match(/^\s*(?:([\s\S]+?)\s+as\s+)?([\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
118
+
119
+ if (!match) {
120
+ throw uiSelectMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.",
121
+ expression);
122
+ }
123
+
124
+ return {
125
+ itemName: match[2], // (lhs) Left-hand side,
126
+ source: $parse(match[3]),
127
+ trackByExp: match[4],
128
+ modelMapper: $parse(match[1] || match[2])
129
+ };
130
+
131
+ };
132
+
133
+ self.getGroupNgRepeatExpression = function() {
134
+ return '$group in $select.groups';
135
+ };
136
+
137
+ self.getNgRepeatExpression = function(itemName, source, trackByExp, grouped) {
138
+ var expression = itemName + ' in ' + (grouped ? '$group.items' : source);
139
+ if (trackByExp) {
140
+ expression += ' track by ' + trackByExp;
141
+ }
142
+ return expression;
143
+ };
144
+ }])
145
+
146
+ /**
147
+ * Contains ui-select "intelligence".
148
+ *
149
+ * The goal is to limit dependency on the DOM whenever possible and
150
+ * put as much logic in the controller (instead of the link functions) as possible so it can be easily tested.
151
+ */
152
+ .controller('uiSelectCtrl',
153
+ ['$scope', '$element', '$timeout', '$filter', 'RepeatParser', 'uiSelectMinErr', 'uiSelectConfig',
154
+ function($scope, $element, $timeout, $filter, RepeatParser, uiSelectMinErr, uiSelectConfig) {
155
+
156
+ var ctrl = this;
157
+
158
+ var EMPTY_SEARCH = '';
159
+
160
+ ctrl.placeholder = undefined;
161
+ ctrl.search = EMPTY_SEARCH;
162
+ ctrl.activeIndex = 0;
163
+ ctrl.activeMatchIndex = -1;
164
+ ctrl.items = [];
165
+ ctrl.selected = undefined;
166
+ ctrl.open = false;
167
+ ctrl.focus = false;
168
+ ctrl.focusser = undefined; //Reference to input element used to handle focus events
169
+ ctrl.disabled = undefined; // Initialized inside uiSelect directive link function
170
+ ctrl.searchEnabled = undefined; // Initialized inside uiSelect directive link function
171
+ ctrl.resetSearchInput = undefined; // Initialized inside uiSelect directive link function
172
+ ctrl.refreshDelay = undefined; // Initialized inside uiSelectChoices directive link function
173
+ ctrl.multiple = false; // Initialized inside uiSelect directive link function
174
+ ctrl.disableChoiceExpression = undefined; // Initialized inside uiSelect directive link function
175
+ ctrl.tagging = {isActivated: false, fct: undefined};
176
+ ctrl.taggingTokens = {isActivated: false, tokens: undefined};
177
+ ctrl.lockChoiceExpression = undefined; // Initialized inside uiSelect directive link function
178
+ ctrl.closeOnSelect = true; // Initialized inside uiSelect directive link function
179
+ ctrl.clickTriggeredSelect = false;
180
+ ctrl.$filter = $filter;
181
+
182
+ ctrl.isEmpty = function() {
183
+ return angular.isUndefined(ctrl.selected) || ctrl.selected === null || ctrl.selected === '';
184
+ };
185
+
186
+ var _searchInput = $element.querySelectorAll('input.ui-select-search');
187
+ if (_searchInput.length !== 1) {
188
+ throw uiSelectMinErr('searchInput', "Expected 1 input.ui-select-search but got '{0}'.", _searchInput.length);
189
+ }
190
+
191
+ // Most of the time the user does not want to empty the search input when in typeahead mode
192
+ function _resetSearchInput() {
193
+ if (ctrl.resetSearchInput || (ctrl.resetSearchInput === undefined && uiSelectConfig.resetSearchInput)) {
194
+ ctrl.search = EMPTY_SEARCH;
195
+ //reset activeIndex
196
+ if (ctrl.selected && ctrl.items.length && !ctrl.multiple) {
197
+ ctrl.activeIndex = ctrl.items.indexOf(ctrl.selected);
198
+ }
199
+ }
200
+ }
201
+
202
+ // When the user clicks on ui-select, displays the dropdown list
203
+ ctrl.activate = function(initSearchValue, avoidReset) {
204
+ if (!ctrl.disabled && !ctrl.open) {
205
+ if(!avoidReset) _resetSearchInput();
206
+ ctrl.focusser.prop('disabled', true); //Will reactivate it on .close()
207
+ ctrl.open = true;
208
+ ctrl.activeMatchIndex = -1;
209
+
210
+ ctrl.activeIndex = ctrl.activeIndex >= ctrl.items.length ? 0 : ctrl.activeIndex;
211
+
212
+ // ensure that the index is set to zero for tagging variants
213
+ // that where first option is auto-selected
214
+ if ( ctrl.activeIndex === -1 && ctrl.taggingLabel !== false ) {
215
+ ctrl.activeIndex = 0;
216
+ }
217
+
218
+ // Give it time to appear before focus
219
+ $timeout(function() {
220
+ ctrl.search = initSearchValue || ctrl.search;
221
+ _searchInput[0].focus();
222
+ });
223
+ }
224
+ };
225
+
226
+ ctrl.findGroupByName = function(name) {
227
+ return ctrl.groups && ctrl.groups.filter(function(group) {
228
+ return group.name === name;
229
+ })[0];
230
+ };
231
+
232
+ ctrl.parseRepeatAttr = function(repeatAttr, groupByExp) {
233
+ function updateGroups(items) {
234
+ ctrl.groups = [];
235
+ angular.forEach(items, function(item) {
236
+ var groupFn = $scope.$eval(groupByExp);
237
+ var groupName = angular.isFunction(groupFn) ? groupFn(item) : item[groupFn];
238
+ var group = ctrl.findGroupByName(groupName);
239
+ if(group) {
240
+ group.items.push(item);
241
+ }
242
+ else {
243
+ ctrl.groups.push({name: groupName, items: [item]});
244
+ }
245
+ });
246
+ ctrl.items = [];
247
+ ctrl.groups.forEach(function(group) {
248
+ ctrl.items = ctrl.items.concat(group.items);
249
+ });
250
+ }
251
+
252
+ function setPlainItems(items) {
253
+ ctrl.items = items;
254
+ }
255
+
256
+ var setItemsFn = groupByExp ? updateGroups : setPlainItems;
257
+
258
+ ctrl.parserResult = RepeatParser.parse(repeatAttr);
259
+
260
+ ctrl.isGrouped = !!groupByExp;
261
+ ctrl.itemProperty = ctrl.parserResult.itemName;
262
+
263
+ // See https://github.com/angular/angular.js/blob/v1.2.15/src/ng/directive/ngRepeat.js#L259
264
+ $scope.$watchCollection(ctrl.parserResult.source, function(items) {
265
+
266
+ if (items === undefined || items === null) {
267
+ // If the user specifies undefined or null => reset the collection
268
+ // Special case: items can be undefined if the user did not initialized the collection on the scope
269
+ // i.e $scope.addresses = [] is missing
270
+ ctrl.items = [];
271
+ } else {
272
+ if (!angular.isArray(items)) {
273
+ throw uiSelectMinErr('items', "Expected an array but got '{0}'.", items);
274
+ } else {
275
+ if (ctrl.multiple){
276
+ //Remove already selected items (ex: while searching)
277
+ var filteredItems = items.filter(function(i) {return ctrl.selected.indexOf(i) < 0;});
278
+ setItemsFn(filteredItems);
279
+ }else{
280
+ setItemsFn(items);
281
+ }
282
+ ctrl.ngModel.$modelValue = null; //Force scope model value and ngModel value to be out of sync to re-run formatters
283
+
284
+ }
285
+ }
286
+
287
+ });
288
+
289
+ if (ctrl.multiple){
290
+ //Remove already selected items
291
+ $scope.$watchCollection('$select.selected', function(selectedItems){
292
+ var data = ctrl.parserResult.source($scope);
293
+ if (!selectedItems.length) {
294
+ setItemsFn(data);
295
+ }else{
296
+ if ( data !== undefined ) {
297
+ var filteredItems = data.filter(function(i) {return selectedItems.indexOf(i) < 0;});
298
+ setItemsFn(filteredItems);
299
+ }
300
+ }
301
+ ctrl.sizeSearchInput();
302
+ });
303
+ }
304
+
305
+ };
306
+
307
+ var _refreshDelayPromise;
308
+
309
+ /**
310
+ * Typeahead mode: lets the user refresh the collection using his own function.
311
+ *
312
+ * See Expose $select.search for external / remote filtering https://github.com/angular-ui/ui-select/pull/31
313
+ */
314
+ ctrl.refresh = function(refreshAttr) {
315
+ if (refreshAttr !== undefined) {
316
+
317
+ // Debounce
318
+ // See https://github.com/angular-ui/bootstrap/blob/0.10.0/src/typeahead/typeahead.js#L155
319
+ // FYI AngularStrap typeahead does not have debouncing: https://github.com/mgcrea/angular-strap/blob/v2.0.0-rc.4/src/typeahead/typeahead.js#L177
320
+ if (_refreshDelayPromise) {
321
+ $timeout.cancel(_refreshDelayPromise);
322
+ }
323
+ _refreshDelayPromise = $timeout(function() {
324
+ $scope.$eval(refreshAttr);
325
+ }, ctrl.refreshDelay);
326
+ }
327
+ };
328
+
329
+ ctrl.setActiveItem = function(item) {
330
+ ctrl.activeIndex = ctrl.items.indexOf(item);
331
+ };
332
+
333
+ ctrl.isActive = function(itemScope) {
334
+ if ( !ctrl.open ) {
335
+ return false;
336
+ }
337
+ var itemIndex = ctrl.items.indexOf(itemScope[ctrl.itemProperty]);
338
+ var isActive = itemIndex === ctrl.activeIndex;
339
+
340
+ if ( !isActive || ( itemIndex < 0 && ctrl.taggingLabel !== false ) ||( itemIndex < 0 && ctrl.taggingLabel === false) ) {
341
+ return false;
342
+ }
343
+
344
+ if (isActive && !angular.isUndefined(ctrl.onHighlightCallback)) {
345
+ itemScope.$eval(ctrl.onHighlightCallback);
346
+ }
347
+
348
+ return isActive;
349
+ };
350
+
351
+ ctrl.isDisabled = function(itemScope) {
352
+
353
+ if (!ctrl.open) return;
354
+
355
+ var itemIndex = ctrl.items.indexOf(itemScope[ctrl.itemProperty]);
356
+ var isDisabled = false;
357
+ var item;
358
+
359
+ if (itemIndex >= 0 && !angular.isUndefined(ctrl.disableChoiceExpression)) {
360
+ item = ctrl.items[itemIndex];
361
+ isDisabled = !!(itemScope.$eval(ctrl.disableChoiceExpression)); // force the boolean value
362
+ item._uiSelectChoiceDisabled = isDisabled; // store this for later reference
363
+ }
364
+
365
+ return isDisabled;
366
+ };
367
+
368
+
369
+ // When the user selects an item with ENTER or clicks the dropdown
370
+ ctrl.select = function(item, skipFocusser, $event) {
371
+ if (item === undefined || !item._uiSelectChoiceDisabled) {
372
+
373
+ if ( ! ctrl.items && ! ctrl.search ) return;
374
+
375
+ if (!item || !item._uiSelectChoiceDisabled) {
376
+ if(ctrl.tagging.isActivated) {
377
+ // if taggingLabel is disabled, we pull from ctrl.search val
378
+ if ( ctrl.taggingLabel === false ) {
379
+ if ( ctrl.activeIndex < 0 ) {
380
+ item = ctrl.tagging.fct !== undefined ? ctrl.tagging.fct(ctrl.search) : ctrl.search;
381
+ if ( angular.equals( ctrl.items[0], item ) ) {
382
+ return;
383
+ }
384
+ } else {
385
+ // keyboard nav happened first, user selected from dropdown
386
+ item = ctrl.items[ctrl.activeIndex];
387
+ }
388
+ } else {
389
+ // tagging always operates at index zero, taggingLabel === false pushes
390
+ // the ctrl.search value without having it injected
391
+ if ( ctrl.activeIndex === 0 ) {
392
+ // ctrl.tagging pushes items to ctrl.items, so we only have empty val
393
+ // for `item` if it is a detected duplicate
394
+ if ( item === undefined ) return;
395
+ // create new item on the fly
396
+ item = ctrl.tagging.fct !== undefined ? ctrl.tagging.fct(ctrl.search) : item.replace(ctrl.taggingLabel,'');
397
+ }
398
+ }
399
+ // search ctrl.selected for dupes potentially caused by tagging and return early if found
400
+ if ( ctrl.selected && ctrl.selected.filter( function (selection) { return angular.equals(selection, item); }).length > 0 ) {
401
+ ctrl.close(skipFocusser);
402
+ return;
403
+ }
404
+ }
405
+
406
+ var locals = {};
407
+ locals[ctrl.parserResult.itemName] = item;
408
+
409
+ ctrl.onSelectCallback($scope, {
410
+ $item: item,
411
+ $model: ctrl.parserResult.modelMapper($scope, locals)
412
+ });
413
+
414
+ if(ctrl.multiple) {
415
+ ctrl.selected.push(item);
416
+ ctrl.sizeSearchInput();
417
+ } else {
418
+ ctrl.selected = item;
419
+ }
420
+ if (!ctrl.multiple || ctrl.closeOnSelect) {
421
+ ctrl.close(skipFocusser);
422
+ }
423
+ if ($event && $event.type === 'click') {
424
+ ctrl.clickTriggeredSelect = true;
425
+ }
426
+ }
427
+ }
428
+ };
429
+
430
+ // Closes the dropdown
431
+ ctrl.close = function(skipFocusser) {
432
+ if (!ctrl.open) return;
433
+ _resetSearchInput();
434
+ ctrl.open = false;
435
+ if (!ctrl.multiple){
436
+ $timeout(function(){
437
+ ctrl.focusser.prop('disabled', false);
438
+ if (!skipFocusser) ctrl.focusser[0].focus();
439
+ },0,false);
440
+ }
441
+ };
442
+
443
+ // Toggle dropdown
444
+ ctrl.toggle = function(e) {
445
+ if (ctrl.open) ctrl.close(); else ctrl.activate();
446
+ e.preventDefault();
447
+ e.stopPropagation();
448
+ };
449
+
450
+ ctrl.isLocked = function(itemScope, itemIndex) {
451
+ var isLocked, item = ctrl.selected[itemIndex];
452
+
453
+ if (item && !angular.isUndefined(ctrl.lockChoiceExpression)) {
454
+ isLocked = !!(itemScope.$eval(ctrl.lockChoiceExpression)); // force the boolean value
455
+ item._uiSelectChoiceLocked = isLocked; // store this for later reference
456
+ }
457
+
458
+ return isLocked;
459
+ };
460
+
461
+ // Remove item from multiple select
462
+ ctrl.removeChoice = function(index){
463
+ var removedChoice = ctrl.selected[index];
464
+
465
+ // if the choice is locked, can't remove it
466
+ if(removedChoice._uiSelectChoiceLocked) return;
467
+
468
+ var locals = {};
469
+ locals[ctrl.parserResult.itemName] = removedChoice;
470
+
471
+ ctrl.selected.splice(index, 1);
472
+ ctrl.activeMatchIndex = -1;
473
+ ctrl.sizeSearchInput();
474
+
475
+ ctrl.onRemoveCallback($scope, {
476
+ $item: removedChoice,
477
+ $model: ctrl.parserResult.modelMapper($scope, locals)
478
+ });
479
+ };
480
+
481
+ ctrl.getPlaceholder = function(){
482
+ //Refactor single?
483
+ if(ctrl.multiple && ctrl.selected.length) return;
484
+ return ctrl.placeholder;
485
+ };
486
+
487
+ var containerSizeWatch;
488
+ ctrl.sizeSearchInput = function(){
489
+ var input = _searchInput[0],
490
+ container = _searchInput.parent().parent()[0];
491
+ _searchInput.css('width','10px');
492
+ var calculate = function(){
493
+ var newWidth = container.clientWidth - input.offsetLeft - 10;
494
+ if(newWidth < 50) newWidth = container.clientWidth;
495
+ _searchInput.css('width',newWidth+'px');
496
+ };
497
+ $timeout(function(){ //Give tags time to render correctly
498
+ if (container.clientWidth === 0 && !containerSizeWatch){
499
+ containerSizeWatch = $scope.$watch(function(){ return container.clientWidth;}, function(newValue){
500
+ if (newValue !== 0){
501
+ calculate();
502
+ containerSizeWatch();
503
+ containerSizeWatch = null;
504
+ }
505
+ });
506
+ }else if (!containerSizeWatch) {
507
+ calculate();
508
+ }
509
+ }, 0, false);
510
+ };
511
+
512
+ function _handleDropDownSelection(key) {
513
+ var processed = true;
514
+ switch (key) {
515
+ case KEY.DOWN:
516
+ if (!ctrl.open && ctrl.multiple) ctrl.activate(false, true); //In case its the search input in 'multiple' mode
517
+ else if (ctrl.activeIndex < ctrl.items.length - 1) { ctrl.activeIndex++; }
518
+ break;
519
+ case KEY.UP:
520
+ if (!ctrl.open && ctrl.multiple) ctrl.activate(false, true); //In case its the search input in 'multiple' mode
521
+ else if (ctrl.activeIndex > 0 || (ctrl.search.length === 0 && ctrl.tagging.isActivated)) { ctrl.activeIndex--; }
522
+ break;
523
+ case KEY.TAB:
524
+ if (!ctrl.multiple || ctrl.open) ctrl.select(ctrl.items[ctrl.activeIndex], true);
525
+ break;
526
+ case KEY.ENTER:
527
+ if(ctrl.open){
528
+ ctrl.select(ctrl.items[ctrl.activeIndex]);
529
+ } else {
530
+ ctrl.activate(false, true); //In case its the search input in 'multiple' mode
531
+ }
532
+ break;
533
+ case KEY.ESC:
534
+ ctrl.close();
535
+ break;
536
+ default:
537
+ processed = false;
538
+ }
539
+ return processed;
540
+ }
541
+
542
+ // Handles selected options in "multiple" mode
543
+ function _handleMatchSelection(key){
544
+ var caretPosition = _getCaretPosition(_searchInput[0]),
545
+ length = ctrl.selected.length,
546
+ // none = -1,
547
+ first = 0,
548
+ last = length-1,
549
+ curr = ctrl.activeMatchIndex,
550
+ next = ctrl.activeMatchIndex+1,
551
+ prev = ctrl.activeMatchIndex-1,
552
+ newIndex = curr;
553
+
554
+ if(caretPosition > 0 || (ctrl.search.length && key == KEY.RIGHT)) return false;
555
+
556
+ ctrl.close();
557
+
558
+ function getNewActiveMatchIndex(){
559
+ switch(key){
560
+ case KEY.LEFT:
561
+ // Select previous/first item
562
+ if(~ctrl.activeMatchIndex) return prev;
563
+ // Select last item
564
+ else return last;
565
+ break;
566
+ case KEY.RIGHT:
567
+ // Open drop-down
568
+ if(!~ctrl.activeMatchIndex || curr === last){
569
+ ctrl.activate();
570
+ return false;
571
+ }
572
+ // Select next/last item
573
+ else return next;
574
+ break;
575
+ case KEY.BACKSPACE:
576
+ // Remove selected item and select previous/first
577
+ if(~ctrl.activeMatchIndex){
578
+ ctrl.removeChoice(curr);
579
+ return prev;
580
+ }
581
+ // Select last item
582
+ else return last;
583
+ break;
584
+ case KEY.DELETE:
585
+ // Remove selected item and select next item
586
+ if(~ctrl.activeMatchIndex){
587
+ ctrl.removeChoice(ctrl.activeMatchIndex);
588
+ return curr;
589
+ }
590
+ else return false;
591
+ }
592
+ }
593
+
594
+ newIndex = getNewActiveMatchIndex();
595
+
596
+ if(!ctrl.selected.length || newIndex === false) ctrl.activeMatchIndex = -1;
597
+ else ctrl.activeMatchIndex = Math.min(last,Math.max(first,newIndex));
598
+
599
+ return true;
600
+ }
601
+
602
+ // Bind to keyboard shortcuts
603
+ _searchInput.on('keydown', function(e) {
604
+
605
+ var key = e.which;
606
+
607
+ // if(~[KEY.ESC,KEY.TAB].indexOf(key)){
608
+ // //TODO: SEGURO?
609
+ // ctrl.close();
610
+ // }
611
+
612
+ $scope.$apply(function() {
613
+ var processed = false;
614
+
615
+ if(ctrl.multiple && KEY.isHorizontalMovement(key)){
616
+ processed = _handleMatchSelection(key);
617
+ }
618
+
619
+ if (!processed && (ctrl.items.length > 0 || ctrl.tagging.isActivated)) {
620
+ processed = _handleDropDownSelection(key);
621
+ if ( ctrl.taggingTokens.isActivated ) {
622
+ for (var i = 0; i < ctrl.taggingTokens.tokens.length; i++) {
623
+ if ( ctrl.taggingTokens.tokens[i] === KEY.MAP[e.keyCode] ) {
624
+ // make sure there is a new value to push via tagging
625
+ if ( ctrl.search.length > 0 ) {
626
+ ctrl.select(null, true);
627
+ _searchInput.triggerHandler('tagged');
628
+ }
629
+ }
630
+ }
631
+ }
632
+ }
633
+
634
+ if (processed && key != KEY.TAB) {
635
+ //TODO Check si el tab selecciona aun correctamente
636
+ //Crear test
637
+ e.preventDefault();
638
+ e.stopPropagation();
639
+ }
640
+ });
641
+
642
+ if(KEY.isVerticalMovement(key) && ctrl.items.length > 0){
643
+ _ensureHighlightVisible();
644
+ }
645
+
646
+ });
647
+
648
+ _searchInput.on('keyup', function(e) {
649
+ if ( ! KEY.isVerticalMovement(e.which) ) {
650
+ $scope.$evalAsync( function () {
651
+ ctrl.activeIndex = ctrl.taggingLabel === false ? -1 : 0;
652
+ });
653
+ }
654
+ // Push a "create new" item into array if there is a search string
655
+ if ( ctrl.tagging.isActivated && ctrl.search.length > 0 ) {
656
+
657
+ // return early with these keys
658
+ if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC || KEY.isVerticalMovement(e.which) ) {
659
+ return;
660
+ }
661
+ // always reset the activeIndex to the first item when tagging
662
+ ctrl.activeIndex = ctrl.taggingLabel === false ? -1 : 0;
663
+ // taggingLabel === false bypasses all of this
664
+ if (ctrl.taggingLabel === false) return;
665
+
666
+ var items = angular.copy( ctrl.items );
667
+ var stashArr = angular.copy( ctrl.items );
668
+ var newItem;
669
+ var item;
670
+ var hasTag = false;
671
+ var dupeIndex = -1;
672
+ var tagItems;
673
+ var tagItem;
674
+
675
+ // case for object tagging via transform `ctrl.tagging.fct` function
676
+ if ( ctrl.tagging.fct !== undefined) {
677
+ tagItems = ctrl.$filter('filter')(items,{'isTag': true});
678
+ if ( tagItems.length > 0 ) {
679
+ tagItem = tagItems[0];
680
+ }
681
+ // remove the first element, if it has the `isTag` prop we generate a new one with each keyup, shaving the previous
682
+ if ( items.length > 0 && tagItem ) {
683
+ hasTag = true;
684
+ items = items.slice(1,items.length);
685
+ stashArr = stashArr.slice(1,stashArr.length);
686
+ }
687
+ newItem = ctrl.tagging.fct(ctrl.search);
688
+ newItem.isTag = true;
689
+ // verify the the tag doesn't match the value of an existing item
690
+ if ( stashArr.filter( function (origItem) { return angular.equals( origItem, ctrl.tagging.fct(ctrl.search) ); } ).length > 0 ) {
691
+ return;
692
+ }
693
+ // handle newItem string and stripping dupes in tagging string context
694
+ } else {
695
+ // find any tagging items already in the ctrl.items array and store them
696
+ tagItems = ctrl.$filter('filter')(items,function (item) {
697
+ return item.match(ctrl.taggingLabel);
698
+ });
699
+ if ( tagItems.length > 0 ) {
700
+ tagItem = tagItems[0];
701
+ }
702
+ item = items[0];
703
+ // remove existing tag item if found (should only ever be one tag item)
704
+ if ( item !== undefined && items.length > 0 && tagItem ) {
705
+ hasTag = true;
706
+ items = items.slice(1,items.length);
707
+ stashArr = stashArr.slice(1,stashArr.length);
708
+ }
709
+ newItem = ctrl.search+' '+ctrl.taggingLabel;
710
+ if ( _findApproxDupe(ctrl.selected, ctrl.search) > -1 ) {
711
+ return;
712
+ }
713
+ // verify the the tag doesn't match the value of an existing item from
714
+ // the searched data set
715
+ if ( stashArr.filter( function (origItem) { return origItem.toUpperCase() === ctrl.search.toUpperCase(); }).length > 0 ) {
716
+ // if there is a tag from prev iteration, strip it / queue the change
717
+ // and return early
718
+ if ( hasTag ) {
719
+ items = stashArr;
720
+ $scope.$evalAsync( function () {
721
+ ctrl.activeIndex = 0;
722
+ ctrl.items = items;
723
+ });
724
+ }
725
+ return;
726
+ }
727
+ if ( ctrl.selected.filter( function (selection) { return selection.toUpperCase() === ctrl.search.toUpperCase(); } ).length > 0 ) {
728
+ // if there is a tag from prev iteration, strip it
729
+ if ( hasTag ) {
730
+ ctrl.items = stashArr.slice(1,stashArr.length);
731
+ }
732
+ return;
733
+ }
734
+ }
735
+ if ( hasTag ) dupeIndex = _findApproxDupe(ctrl.selected, newItem);
736
+ // dupe found, shave the first item
737
+ if ( dupeIndex > -1 ) {
738
+ items = items.slice(dupeIndex+1,items.length-1);
739
+ } else {
740
+ items = [];
741
+ items.push(newItem);
742
+ items = items.concat(stashArr);
743
+ }
744
+ $scope.$evalAsync( function () {
745
+ ctrl.activeIndex = 0;
746
+ ctrl.items = items;
747
+ });
748
+ }
749
+ });
750
+
751
+ _searchInput.on('tagged', function() {
752
+ $timeout(function() {
753
+ _resetSearchInput();
754
+ });
755
+ });
756
+
757
+ _searchInput.on('blur', function() {
758
+ $timeout(function() {
759
+ ctrl.activeMatchIndex = -1;
760
+ });
761
+ });
762
+
763
+ function _findApproxDupe(haystack, needle) {
764
+ var tempArr = angular.copy(haystack);
765
+ var dupeIndex = -1;
766
+ for (var i = 0; i <tempArr.length; i++) {
767
+ // handle the simple string version of tagging
768
+ if ( ctrl.tagging.fct === undefined ) {
769
+ // search the array for the match
770
+ if ( tempArr[i]+' '+ctrl.taggingLabel === needle ) {
771
+ dupeIndex = i;
772
+ }
773
+ // handle the object tagging implementation
774
+ } else {
775
+ var mockObj = tempArr[i];
776
+ mockObj.isTag = true;
777
+ if ( angular.equals(mockObj, needle) ) {
778
+ dupeIndex = i;
779
+ }
780
+ }
781
+ }
782
+ return dupeIndex;
783
+ }
784
+
785
+ function _getCaretPosition(el) {
786
+ if(angular.isNumber(el.selectionStart)) return el.selectionStart;
787
+ // selectionStart is not supported in IE8 and we don't want hacky workarounds so we compromise
788
+ else return el.value.length;
789
+ }
790
+
791
+ // See https://github.com/ivaynberg/select2/blob/3.4.6/select2.js#L1431
792
+ function _ensureHighlightVisible() {
793
+ var container = $element.querySelectorAll('.ui-select-choices-content');
794
+ var choices = container.querySelectorAll('.ui-select-choices-row');
795
+ if (choices.length < 1) {
796
+ throw uiSelectMinErr('choices', "Expected multiple .ui-select-choices-row but got '{0}'.", choices.length);
797
+ }
798
+
799
+ var highlighted = choices[ctrl.activeIndex];
800
+ var posY = highlighted.offsetTop + highlighted.clientHeight - container[0].scrollTop;
801
+ var height = container[0].offsetHeight;
802
+
803
+ if (posY > height) {
804
+ container[0].scrollTop += posY - height;
805
+ } else if (posY < highlighted.clientHeight) {
806
+ if (ctrl.isGrouped && ctrl.activeIndex === 0)
807
+ container[0].scrollTop = 0; //To make group header visible when going all the way up
808
+ else
809
+ container[0].scrollTop -= highlighted.clientHeight - posY;
810
+ }
811
+ }
812
+
813
+ $scope.$on('$destroy', function() {
814
+ _searchInput.off('keyup keydown tagged blur');
815
+ });
816
+ }])
817
+
818
+ .directive('uiSelect',
819
+ ['$document', 'uiSelectConfig', 'uiSelectMinErr', '$compile', '$parse',
820
+ function($document, uiSelectConfig, uiSelectMinErr, $compile, $parse) {
821
+
822
+ return {
823
+ restrict: 'EA',
824
+ templateUrl: function(tElement, tAttrs) {
825
+ var theme = tAttrs.theme || uiSelectConfig.theme;
826
+ return theme + (angular.isDefined(tAttrs.multiple) ? '/select-multiple.tpl.html' : '/select.tpl.html');
827
+ },
828
+ replace: true,
829
+ transclude: true,
830
+ require: ['uiSelect', 'ngModel'],
831
+ scope: true,
832
+
833
+ controller: 'uiSelectCtrl',
834
+ controllerAs: '$select',
835
+
836
+ link: function(scope, element, attrs, ctrls, transcludeFn) {
837
+ var $select = ctrls[0];
838
+ var ngModel = ctrls[1];
839
+
840
+ var searchInput = element.querySelectorAll('input.ui-select-search');
841
+
842
+ $select.multiple = angular.isDefined(attrs.multiple) && (
843
+ attrs.multiple === '' ||
844
+ attrs.multiple.toLowerCase() === 'multiple' ||
845
+ attrs.multiple.toLowerCase() === 'true'
846
+ );
847
+
848
+ $select.closeOnSelect = (angular.isDefined(attrs.closeOnSelect) && attrs.closeOnSelect.toLowerCase() === 'false') ? false : uiSelectConfig.closeOnSelect;
849
+ $select.onSelectCallback = $parse(attrs.onSelect);
850
+ $select.onRemoveCallback = $parse(attrs.onRemove);
851
+
852
+ //From view --> model
853
+ ngModel.$parsers.unshift(function (inputValue) {
854
+ var locals = {},
855
+ result;
856
+ if ($select.multiple){
857
+ var resultMultiple = [];
858
+ for (var j = $select.selected.length - 1; j >= 0; j--) {
859
+ locals = {};
860
+ locals[$select.parserResult.itemName] = $select.selected[j];
861
+ result = $select.parserResult.modelMapper(scope, locals);
862
+ resultMultiple.unshift(result);
863
+ }
864
+ return resultMultiple;
865
+ }else{
866
+ locals = {};
867
+ locals[$select.parserResult.itemName] = inputValue;
868
+ result = $select.parserResult.modelMapper(scope, locals);
869
+ return result;
870
+ }
871
+ });
872
+
873
+ //From model --> view
874
+ ngModel.$formatters.unshift(function (inputValue) {
875
+ var data = $select.parserResult.source (scope, { $select : {search:''}}), //Overwrite $search
876
+ locals = {},
877
+ result;
878
+ if (data){
879
+ if ($select.multiple){
880
+ var resultMultiple = [];
881
+ var checkFnMultiple = function(list, value){
882
+ if (!list || !list.length) return;
883
+ for (var p = list.length - 1; p >= 0; p--) {
884
+ locals[$select.parserResult.itemName] = list[p];
885
+ result = $select.parserResult.modelMapper(scope, locals);
886
+ if (result == value){
887
+ resultMultiple.unshift(list[p]);
888
+ return true;
889
+ }
890
+ }
891
+ return false;
892
+ };
893
+ if (!inputValue) return resultMultiple; //If ngModel was undefined
894
+ for (var k = inputValue.length - 1; k >= 0; k--) {
895
+ if (!checkFnMultiple($select.selected, inputValue[k])){
896
+ checkFnMultiple(data, inputValue[k]);
897
+ }
898
+ }
899
+ return resultMultiple;
900
+ }else{
901
+ var checkFnSingle = function(d){
902
+ locals[$select.parserResult.itemName] = d;
903
+ result = $select.parserResult.modelMapper(scope, locals);
904
+ return result == inputValue;
905
+ };
906
+ //If possible pass same object stored in $select.selected
907
+ if ($select.selected && checkFnSingle($select.selected)) {
908
+ return $select.selected;
909
+ }
910
+ for (var i = data.length - 1; i >= 0; i--) {
911
+ if (checkFnSingle(data[i])) return data[i];
912
+ }
913
+ }
914
+ }
915
+ return inputValue;
916
+ });
917
+
918
+ //Set reference to ngModel from uiSelectCtrl
919
+ $select.ngModel = ngModel;
920
+
921
+ //Idea from: https://github.com/ivaynberg/select2/blob/79b5bf6db918d7560bdd959109b7bcfb47edaf43/select2.js#L1954
922
+ var focusser = angular.element("<input ng-disabled='$select.disabled' class='ui-select-focusser ui-select-offscreen' type='text' aria-haspopup='true' role='button' />");
923
+
924
+ if(attrs.tabindex){
925
+ //tabindex might be an expression, wait until it contains the actual value before we set the focusser tabindex
926
+ attrs.$observe('tabindex', function(value) {
927
+ //If we are using multiple, add tabindex to the search input
928
+ if($select.multiple){
929
+ searchInput.attr("tabindex", value);
930
+ } else {
931
+ focusser.attr("tabindex", value);
932
+ }
933
+ //Remove the tabindex on the parent so that it is not focusable
934
+ element.removeAttr("tabindex");
935
+ });
936
+ }
937
+
938
+ $compile(focusser)(scope);
939
+ $select.focusser = focusser;
940
+
941
+ if (!$select.multiple){
942
+
943
+ element.append(focusser);
944
+ focusser.bind("focus", function(){
945
+ scope.$evalAsync(function(){
946
+ $select.focus = true;
947
+ });
948
+ });
949
+ focusser.bind("blur", function(){
950
+ scope.$evalAsync(function(){
951
+ $select.focus = false;
952
+ });
953
+ });
954
+ focusser.bind("keydown", function(e){
955
+
956
+ if (e.which === KEY.BACKSPACE) {
957
+ e.preventDefault();
958
+ e.stopPropagation();
959
+ $select.select(undefined);
960
+ scope.$apply();
961
+ return;
962
+ }
963
+
964
+ if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) {
965
+ return;
966
+ }
967
+
968
+ if (e.which == KEY.DOWN || e.which == KEY.UP || e.which == KEY.ENTER || e.which == KEY.SPACE){
969
+ e.preventDefault();
970
+ e.stopPropagation();
971
+ $select.activate();
972
+ }
973
+
974
+ scope.$digest();
975
+ });
976
+
977
+ focusser.bind("keyup input", function(e){
978
+
979
+ if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC || e.which == KEY.ENTER || e.which === KEY.BACKSPACE) {
980
+ return;
981
+ }
982
+
983
+ $select.activate(focusser.val()); //User pressed some regular key, so we pass it to the search input
984
+ focusser.val('');
985
+ scope.$digest();
986
+
987
+ });
988
+
989
+ }
990
+
991
+
992
+ scope.$watch('searchEnabled', function() {
993
+ var searchEnabled = scope.$eval(attrs.searchEnabled);
994
+ $select.searchEnabled = searchEnabled !== undefined ? searchEnabled : uiSelectConfig.searchEnabled;
995
+ });
996
+
997
+ attrs.$observe('disabled', function() {
998
+ // No need to use $eval() (thanks to ng-disabled) since we already get a boolean instead of a string
999
+ $select.disabled = attrs.disabled !== undefined ? attrs.disabled : false;
1000
+ });
1001
+
1002
+ attrs.$observe('resetSearchInput', function() {
1003
+ // $eval() is needed otherwise we get a string instead of a boolean
1004
+ var resetSearchInput = scope.$eval(attrs.resetSearchInput);
1005
+ $select.resetSearchInput = resetSearchInput !== undefined ? resetSearchInput : true;
1006
+ });
1007
+
1008
+ attrs.$observe('tagging', function() {
1009
+ if(attrs.tagging !== undefined)
1010
+ {
1011
+ // $eval() is needed otherwise we get a string instead of a boolean
1012
+ var taggingEval = scope.$eval(attrs.tagging);
1013
+ $select.tagging = {isActivated: true, fct: taggingEval !== true ? taggingEval : undefined};
1014
+ }
1015
+ else
1016
+ {
1017
+ $select.tagging = {isActivated: false, fct: undefined};
1018
+ }
1019
+ });
1020
+
1021
+ attrs.$observe('taggingLabel', function() {
1022
+ if(attrs.tagging !== undefined && attrs.taggingLabel !== undefined)
1023
+ {
1024
+ // check eval for FALSE, in this case, we disable the labels
1025
+ // associated with tagging
1026
+ if ( attrs.taggingLabel === 'false' ) {
1027
+ $select.taggingLabel = false;
1028
+ }
1029
+ else
1030
+ {
1031
+ $select.taggingLabel = attrs.taggingLabel !== undefined ? attrs.taggingLabel : '(new)';
1032
+ }
1033
+ }
1034
+ });
1035
+
1036
+ attrs.$observe('taggingTokens', function() {
1037
+ if (attrs.tagging !== undefined) {
1038
+ var tokens = attrs.taggingTokens !== undefined ? attrs.taggingTokens.split('|') : [',','ENTER'];
1039
+ $select.taggingTokens = {isActivated: true, tokens: tokens };
1040
+ }
1041
+ });
1042
+
1043
+ if ($select.multiple){
1044
+ scope.$watchCollection(function(){ return ngModel.$modelValue; }, function(newValue, oldValue) {
1045
+ if (oldValue != newValue)
1046
+ ngModel.$modelValue = null; //Force scope model value and ngModel value to be out of sync to re-run formatters
1047
+ });
1048
+ scope.$watchCollection('$select.selected', function() {
1049
+ ngModel.$setViewValue(Date.now()); //Set timestamp as a unique string to force changes
1050
+ });
1051
+ focusser.prop('disabled', true); //Focusser isn't needed if multiple
1052
+ }else{
1053
+ scope.$watch('$select.selected', function(newValue) {
1054
+ if (ngModel.$viewValue !== newValue) {
1055
+ ngModel.$setViewValue(newValue);
1056
+ }
1057
+ });
1058
+ }
1059
+
1060
+ ngModel.$render = function() {
1061
+ if($select.multiple){
1062
+ // Make sure that model value is array
1063
+ if(!angular.isArray(ngModel.$viewValue)){
1064
+ // Have tolerance for null or undefined values
1065
+ if(angular.isUndefined(ngModel.$viewValue) || ngModel.$viewValue === null){
1066
+ $select.selected = [];
1067
+ } else {
1068
+ throw uiSelectMinErr('multiarr', "Expected model value to be array but got '{0}'", ngModel.$viewValue);
1069
+ }
1070
+ }
1071
+ }
1072
+ $select.selected = ngModel.$viewValue;
1073
+ };
1074
+
1075
+ function onDocumentClick(e) {
1076
+ var contains = false;
1077
+
1078
+ if (window.jQuery) {
1079
+ // Firefox 3.6 does not support element.contains()
1080
+ // See Node.contains https://developer.mozilla.org/en-US/docs/Web/API/Node.contains
1081
+ contains = window.jQuery.contains(element[0], e.target);
1082
+ } else {
1083
+ contains = element[0].contains(e.target);
1084
+ }
1085
+
1086
+ if (!contains && !$select.clickTriggeredSelect) {
1087
+ $select.close();
1088
+ scope.$digest();
1089
+ }
1090
+ $select.clickTriggeredSelect = false;
1091
+ }
1092
+
1093
+ // See Click everywhere but here event http://stackoverflow.com/questions/12931369
1094
+ $document.on('click', onDocumentClick);
1095
+
1096
+ scope.$on('$destroy', function() {
1097
+ $document.off('click', onDocumentClick);
1098
+ });
1099
+
1100
+ // Move transcluded elements to their correct position in main template
1101
+ transcludeFn(scope, function(clone) {
1102
+ // See Transclude in AngularJS http://blog.omkarpatil.com/2012/11/transclude-in-angularjs.html
1103
+
1104
+ // One day jqLite will be replaced by jQuery and we will be able to write:
1105
+ // var transcludedElement = clone.filter('.my-class')
1106
+ // instead of creating a hackish DOM element:
1107
+ var transcluded = angular.element('<div>').append(clone);
1108
+
1109
+ var transcludedMatch = transcluded.querySelectorAll('.ui-select-match');
1110
+ transcludedMatch.removeAttr('ui-select-match'); //To avoid loop in case directive as attr
1111
+ if (transcludedMatch.length !== 1) {
1112
+ throw uiSelectMinErr('transcluded', "Expected 1 .ui-select-match but got '{0}'.", transcludedMatch.length);
1113
+ }
1114
+ element.querySelectorAll('.ui-select-match').replaceWith(transcludedMatch);
1115
+
1116
+ var transcludedChoices = transcluded.querySelectorAll('.ui-select-choices');
1117
+ transcludedChoices.removeAttr('ui-select-choices'); //To avoid loop in case directive as attr
1118
+ if (transcludedChoices.length !== 1) {
1119
+ throw uiSelectMinErr('transcluded', "Expected 1 .ui-select-choices but got '{0}'.", transcludedChoices.length);
1120
+ }
1121
+ element.querySelectorAll('.ui-select-choices').replaceWith(transcludedChoices);
1122
+
1123
+ var transcludedFooter = transcluded.querySelectorAll('.ui-select-footer');
1124
+ transcludedFooter.removeAttr('ui-select-footer'); //To avoid loop in case directive as attr
1125
+ if (transcludedFooter.length > 0) {
1126
+ element.querySelectorAll('.ui-select-footer').replaceWith(transcludedFooter);
1127
+ }
1128
+ });
1129
+ }
1130
+ };
1131
+ }])
1132
+
1133
+ .directive('uiSelectChoices',
1134
+ ['uiSelectConfig', 'RepeatParser', 'uiSelectMinErr', '$compile',
1135
+ function(uiSelectConfig, RepeatParser, uiSelectMinErr, $compile) {
1136
+
1137
+ return {
1138
+ restrict: 'EA',
1139
+ require: '^uiSelect',
1140
+ replace: true,
1141
+ transclude: true,
1142
+ templateUrl: function(tElement) {
1143
+ // Gets theme attribute from parent (ui-select)
1144
+ var theme = tElement.parent().attr('theme') || uiSelectConfig.theme;
1145
+ return theme + '/choices.tpl.html';
1146
+ },
1147
+
1148
+ compile: function(tElement, tAttrs) {
1149
+
1150
+ if (!tAttrs.repeat) throw uiSelectMinErr('repeat', "Expected 'repeat' expression.");
1151
+
1152
+ return function link(scope, element, attrs, $select, transcludeFn) {
1153
+
1154
+ // var repeat = RepeatParser.parse(attrs.repeat);
1155
+ var groupByExp = attrs.groupBy;
1156
+
1157
+ $select.parseRepeatAttr(attrs.repeat, groupByExp); //Result ready at $select.parserResult
1158
+
1159
+ $select.disableChoiceExpression = attrs.uiDisableChoice;
1160
+ $select.onHighlightCallback = attrs.onHighlight;
1161
+
1162
+ if(groupByExp) {
1163
+ var groups = element.querySelectorAll('.ui-select-choices-group');
1164
+ if (groups.length !== 1) throw uiSelectMinErr('rows', "Expected 1 .ui-select-choices-group but got '{0}'.", groups.length);
1165
+ groups.attr('ng-repeat', RepeatParser.getGroupNgRepeatExpression());
1166
+ }
1167
+
1168
+ var choices = element.querySelectorAll('.ui-select-choices-row');
1169
+ if (choices.length !== 1) {
1170
+ throw uiSelectMinErr('rows', "Expected 1 .ui-select-choices-row but got '{0}'.", choices.length);
1171
+ }
1172
+
1173
+ choices.attr('ng-repeat', RepeatParser.getNgRepeatExpression($select.parserResult.itemName, '$select.items', $select.parserResult.trackByExp, groupByExp))
1174
+ .attr('ng-if', '$select.open') //Prevent unnecessary watches when dropdown is closed
1175
+ .attr('ng-mouseenter', '$select.setActiveItem('+$select.parserResult.itemName +')')
1176
+ .attr('ng-click', '$select.select(' + $select.parserResult.itemName + ',false,$event)');
1177
+
1178
+ var rowsInner = element.querySelectorAll('.ui-select-choices-row-inner');
1179
+ if (rowsInner.length !== 1) throw uiSelectMinErr('rows', "Expected 1 .ui-select-choices-row-inner but got '{0}'.", rowsInner.length);
1180
+ rowsInner.attr('uis-transclude-append', ''); //Adding uisTranscludeAppend directive to row element after choices element has ngRepeat
1181
+
1182
+ $compile(element, transcludeFn)(scope); //Passing current transcludeFn to be able to append elements correctly from uisTranscludeAppend
1183
+
1184
+ scope.$watch('$select.search', function(newValue) {
1185
+ if(newValue && !$select.open && $select.multiple) $select.activate(false, true);
1186
+ $select.activeIndex = $select.tagging.isActivated ? -1 : 0;
1187
+ $select.refresh(attrs.refresh);
1188
+ });
1189
+
1190
+ attrs.$observe('refreshDelay', function() {
1191
+ // $eval() is needed otherwise we get a string instead of a number
1192
+ var refreshDelay = scope.$eval(attrs.refreshDelay);
1193
+ $select.refreshDelay = refreshDelay !== undefined ? refreshDelay : uiSelectConfig.refreshDelay;
1194
+ });
1195
+ };
1196
+ }
1197
+ };
1198
+ }])
1199
+ // Recreates old behavior of ng-transclude. Used internally.
1200
+ .directive('uisTranscludeAppend', function () {
1201
+ return {
1202
+ link: function (scope, element, attrs, ctrl, transclude) {
1203
+ transclude(scope, function (clone) {
1204
+ element.append(clone);
1205
+ });
1206
+ }
1207
+ };
1208
+ })
1209
+ .directive('uiSelectMatch', ['uiSelectConfig', function(uiSelectConfig) {
1210
+ return {
1211
+ restrict: 'EA',
1212
+ require: '^uiSelect',
1213
+ replace: true,
1214
+ transclude: true,
1215
+ templateUrl: function(tElement) {
1216
+ // Gets theme attribute from parent (ui-select)
1217
+ var theme = tElement.parent().attr('theme') || uiSelectConfig.theme;
1218
+ var multi = tElement.parent().attr('multiple');
1219
+ return theme + (multi ? '/match-multiple.tpl.html' : '/match.tpl.html');
1220
+ },
1221
+ link: function(scope, element, attrs, $select) {
1222
+ $select.lockChoiceExpression = attrs.uiLockChoice;
1223
+ attrs.$observe('placeholder', function(placeholder) {
1224
+ $select.placeholder = placeholder !== undefined ? placeholder : uiSelectConfig.placeholder;
1225
+ });
1226
+
1227
+ $select.allowClear = (angular.isDefined(attrs.allowClear)) ? (attrs.allowClear === '') ? true : (attrs.allowClear.toLowerCase() === 'true') : false;
1228
+
1229
+ if($select.multiple){
1230
+ $select.sizeSearchInput();
1231
+ }
1232
+
1233
+ }
1234
+ };
1235
+ }])
1236
+
1237
+ .directive('uiSelectFooter', ['uiSelectConfig', function(uiSelectConfig) {
1238
+ return {
1239
+ restrict: 'EA',
1240
+ //require: '^uiSelect',
1241
+ replace: true,
1242
+ transclude: true,
1243
+ templateUrl: function(tElement) {
1244
+ // Gets theme attribute from parent (ui-select)
1245
+ var theme = tElement.parent().attr('theme') || uiSelectConfig.theme;
1246
+ return theme + '/footer.tpl.html';
1247
+ }
1248
+ };
1249
+ }])
1250
+
1251
+ /**
1252
+ * Highlights text that matches $select.search.
1253
+ *
1254
+ * Taken from AngularUI Bootstrap Typeahead
1255
+ * See https://github.com/angular-ui/bootstrap/blob/0.10.0/src/typeahead/typeahead.js#L340
1256
+ */
1257
+ .filter('highlight', function() {
1258
+ function escapeRegexp(queryToEscape) {
1259
+ return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
1260
+ }
1261
+
1262
+ return function(matchItem, query) {
1263
+ return query && matchItem ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '<span class="ui-select-highlight">$&</span>') : matchItem;
1264
+ };
1265
+ });
1266
+ }());
1267
+
1268
+ angular.module("ui.select").run(["$templateCache", function($templateCache) {$templateCache.put("bootstrap/choices.tpl.html","<ul class=\"ui-select-choices ui-select-choices-content dropdown-menu\" role=\"menu\" aria-labelledby=\"dLabel\" ng-show=\"$select.items.length > 0\"><li class=\"ui-select-choices-group\"><div class=\"divider\" ng-show=\"$select.isGrouped && $index > 0\"></div><div ng-show=\"$select.isGrouped\" class=\"ui-select-choices-group-label dropdown-header\" ng-bind-html=\"$group.name\"></div><div class=\"ui-select-choices-row\" ng-class=\"{active: $select.isActive(this), disabled: $select.isDisabled(this)}\"><a href=\"javascript:void(0)\" class=\"ui-select-choices-row-inner\"></a></div></li></ul>");
1269
+ $templateCache.put("bootstrap/match-multiple.tpl.html","<span class=\"ui-select-match\"><span ng-repeat=\"$item in $select.selected\"><span style=\"margin-right: 3px;\" class=\"ui-select-match-item btn btn-default btn-xs\" tabindex=\"-1\" type=\"button\" ng-disabled=\"$select.disabled\" ng-click=\"$select.activeMatchIndex = $index;\" ng-class=\"{\'btn-primary\':$select.activeMatchIndex === $index}\"><span class=\"close ui-select-match-close\" ng-hide=\"$select.disabled\" ng-click=\"$select.removeChoice($index)\">&nbsp;&times;</span> <span uis-transclude-append=\"\"></span></span></span></span>");
1270
+ $templateCache.put("bootstrap/match.tpl.html","<button type=\"button\" class=\"btn btn-default form-control ui-select-match\" tabindex=\"-1\" ng-hide=\"$select.open\" ng-disabled=\"$select.disabled\" ng-class=\"{\'btn-default-focus\':$select.focus}\" ;=\"\" ng-click=\"$select.activate()\"><span ng-show=\"$select.isEmpty()\" class=\"text-muted\">{{$select.placeholder}}</span> <span ng-hide=\"$select.isEmpty()\" ng-transclude=\"\"></span> <span class=\"caret ui-select-toggle\" ng-click=\"$select.toggle($event)\"></span></button>");
1271
+ $templateCache.put("bootstrap/select-multiple.tpl.html","<div class=\"ui-select-multiple ui-select-bootstrap dropdown form-control\" ng-class=\"{open: $select.open}\"><div><div class=\"ui-select-match\"></div><input type=\"text\" autocomplete=\"off\" autocorrect=\"off\" autocapitalize=\"off\" spellcheck=\"false\" class=\"ui-select-search input-xs\" placeholder=\"{{$select.getPlaceholder()}}\" ng-disabled=\"$select.disabled\" ng-hide=\"$select.disabled\" ng-click=\"$select.activate()\" ng-model=\"$select.search\"></div><div class=\"ui-select-choices\"></div></div>");
1272
+ $templateCache.put("bootstrap/select.tpl.html","<div class=\"ui-select-bootstrap dropdown\" ng-class=\"{open: $select.open}\"><div class=\"ui-select-match\"></div><input type=\"text\" autocomplete=\"off\" tabindex=\"-1\" class=\"form-control ui-select-search\" placeholder=\"{{$select.placeholder}}\" ng-model=\"$select.search\" ng-show=\"$select.searchEnabled && $select.open\"><div class=\"ui-select-choices\"></div></div>");
1273
+ $templateCache.put("select2/choices.tpl.html","<ul class=\"ui-select-choices ui-select-choices-content select2-results\"><li class=\"ui-select-choices-group\" ng-class=\"{\'select2-result-with-children\': $select.isGrouped}\"><div ng-show=\"$select.isGrouped\" class=\"ui-select-choices-group-label select2-result-label\" ng-bind-html=\"$group.name\"></div><ul ng-class=\"{\'select2-result-sub\': $select.isGrouped, \'select2-result-single\': !$select.isGrouped}\"><li class=\"ui-select-choices-row\" ng-class=\"{\'select2-highlighted\': $select.isActive(this), \'select2-disabled\': $select.isDisabled(this)}\"><div class=\"select2-result-label ui-select-choices-row-inner\"></div></li></ul></li></ul>");
1274
+ $templateCache.put("select2/match-multiple.tpl.html","<span class=\"ui-select-match\"><li class=\"ui-select-match-item select2-search-choice\" ng-repeat=\"$item in $select.selected\" ng-class=\"{\'select2-search-choice-focus\':$select.activeMatchIndex === $index, \'select2-locked\':$select.isLocked(this, $index)}\"><span uis-transclude-append=\"\"></span> <a href=\"javascript:;\" class=\"ui-select-match-close select2-search-choice-close\" ng-click=\"$select.removeChoice($index)\" tabindex=\"-1\"></a></li></span>");
1275
+ $templateCache.put("select2/match.tpl.html","<a class=\"select2-choice ui-select-match\" ng-class=\"{\'select2-default\': $select.isEmpty()}\" ng-click=\"$select.activate()\"><span ng-show=\"$select.isEmpty()\" class=\"select2-chosen\">{{$select.placeholder}}</span> <span ng-hide=\"$select.isEmpty()\" class=\"select2-chosen\" ng-transclude=\"\"></span> <abbr ng-if=\"$select.allowClear && !$select.isEmpty()\" class=\"select2-search-choice-close\" ng-click=\"$select.select(undefined)\"></abbr> <span class=\"select2-arrow ui-select-toggle\" ng-click=\"$select.toggle($event)\"><b></b></span></a>");
1276
+ $templateCache.put("select2/select-multiple.tpl.html","<div class=\"ui-select-multiple select2 select2-container select2-container-multi\" ng-class=\"{\'select2-container-active select2-dropdown-open\': $select.open,\n \'select2-container-disabled\': $select.disabled}\"><ul class=\"select2-choices\"><span class=\"ui-select-match\"></span><li class=\"select2-search-field\"><input type=\"text\" autocomplete=\"off\" autocorrect=\"off\" autocapitalize=\"off\" spellcheck=\"false\" class=\"select2-input ui-select-search\" placeholder=\"{{$select.getPlaceholder()}}\" ng-disabled=\"$select.disabled\" ng-hide=\"$select.disabled\" ng-model=\"$select.search\" ng-click=\"$select.activate()\" style=\"width: 34px;\"></li></ul><div class=\"select2-drop select2-with-searchbox select2-drop-active\" ng-class=\"{\'select2-display-none\': !$select.open}\"><div class=\"ui-select-choices\"></div></div></div>");
1277
+ $templateCache.put("select2/select.tpl.html","<div class=\"select2 select2-container\" ng-class=\"{\'select2-container-active select2-dropdown-open\': $select.open,\n \'select2-container-disabled\': $select.disabled,\n \'select2-container-active\': $select.focus, \n \'select2-allowclear\': $select.allowClear && !$select.isEmpty()}\"><div class=\"ui-select-match\"></div><div class=\"select2-drop select2-with-searchbox select2-drop-active\" ng-class=\"{\'select2-display-none\': !$select.open}\"><div class=\"select2-search\" ng-show=\"$select.searchEnabled\"><input type=\"text\" autocomplete=\"off\" autocorrect=\"off\" autocapitalize=\"off\" spellcheck=\"false\" class=\"ui-select-search select2-input\" ng-model=\"$select.search\"></div><div class=\"ui-select-choices\"></div></div></div>");
1278
+ $templateCache.put("selectize/choices.tpl.html","<div ng-show=\"$select.open\" class=\"ui-select-choices selectize-dropdown single\"><div class=\"ui-select-choices-content selectize-dropdown-content\"><div class=\"ui-select-choices-group optgroup\"><div ng-show=\"$select.isGrouped\" class=\"ui-select-choices-group-label optgroup-header\" ng-bind-html=\"$group.name\"></div><div class=\"ui-select-choices-row\" ng-class=\"{active: $select.isActive(this), disabled: $select.isDisabled(this)}\"><div class=\"option ui-select-choices-row-inner\" data-selectable=\"\"></div></div></div></div><div class=\"ui-select-footer\"></div></div>");
1279
+ $templateCache.put("selectize/footer.tpl.html","<div ng-show=\"$select.open\" class=\"ui-select-footer\" ng-transclude=\"\"></div>");
1280
+ $templateCache.put("selectize/match.tpl.html","<div ng-hide=\"($select.open || $select.isEmpty())\" class=\"ui-select-match\" ng-transclude=\"\"></div>");
1281
+ $templateCache.put("selectize/select.tpl.html","<div class=\"selectize-control single\"><div class=\"selectize-input\" ng-class=\"{\'focus\': $select.open, \'disabled\': $select.disabled, \'selectize-focus\' : $select.focus}\" ng-click=\"$select.activate()\"><div class=\"ui-select-match\"></div><input type=\"text\" autocomplete=\"off\" tabindex=\"-1\" class=\"ui-select-search ui-select-toggle\" ng-click=\"$select.toggle($event)\" placeholder=\"{{$select.placeholder}}\" ng-model=\"$select.search\" ng-hide=\"!$select.searchEnabled || ($select.selected && !$select.open)\" ng-disabled=\"$select.disabled\"></div><div class=\"ui-select-choices\"></div></div>");}]);