blob: ffb3e1e1c5e5d0ad14f9f467d58a95d1a5001bfa [file] [log] [blame]
Kalle Raita30b8a872014-04-03 11:22:31 +03001// Copyright 2015 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15'use strict';
16
17// Directives
18
19angular.module('cherry.directives', [])
20
21.directive('appVersion', ['version', function(version)
22{
23 return function(scope, elm, attrs) {
24 elm.text(version);
25 };
26}])
27
28// When window is resized, compute height of this element accordingly.
29// \note [nuutti] This is pretty hacky, and assumes that there are no
30// other elements below this one. (Specifically, assumes
31// that all vertical space below this element's content
32// is caused by the margins, borders and paddings of
33// this element and and its parents.)
34.directive('sizedByWindow', function($document, $window, $timeout)
35{
36 return function(scope, elem)
37 {
38 var setHeight = function()
39 {
40 var pads = function(e)
41 {
42 if (e.outerHeight)
43 return e.outerHeight(true) - e.height();
44 };
45
46 var allPads = 0;
47 // Compute the size of presumed margins, borders and paddings below
48 // elem.offset().top . We assume that top margins etc. are equal to bottom.
49 allPads += pads(elem); // Both top and bottom for this element (offset begins at margin?)
50 elem.parents().each(function()
51 {
52 allPads += pads($(this))/2; // Only bottom for parent elements.
53 });
54
55 elem.height($(window).height() - elem.offset().top - allPads);
56 };
57
58 angular.element($window).bind('resize', setHeight);
59
60 scope.elemTop = function(){return elem.offset().top;};
61 scope.$watch('elemTop()', setHeight);
62 }
63})
64
65// Horizontally draggable thing, for adjusting the widths of two elements next to each other.
66.directive('sizeDragBar', function($document, $window)
67{
68 return function(scope, barElem, attrs)
69 {
70 var dragAttrs = JSON.parse(attrs.sizeDragBar);
71 var leftElem = $('#'+dragAttrs.left);
72 var rightElem = $('#'+dragAttrs.right);
73 var containerElem = $('#'+dragAttrs.container);
74
75 var barLeftOffset; // Difference between the bar's left side (at the time of mousedown) and mouse's x (page) position.
76 var currentBarLeftRatio; // Current position (in percentages) of the bar's left edge.
77
78 barElem.on('mousedown', function(event)
79 {
80 event.preventDefault();
81 $document.on('mousemove', move);
82 $document.on('mouseup', up);
83 barLeftOffset = leftElem.outerWidth() - event.pageX;
84 });
85
86 function percentStr(ratio)
87 {
88 return ratio*100.0 + '%';
89 }
90
91 function pads(elem)
92 {
93 return elem.outerWidth(true) - elem.width();
94 }
95
96 function setSizes(barLeftRatio)
97 {
98 var barWidthRatio = barElem.outerWidth(true) / containerElem.width();
99 var leftElemWidthRatio = barLeftRatio;
100 var rightElemWidthRatio = 1.0 - barWidthRatio - barLeftRatio;
101
Jarkko Pöyry9ad8b182015-06-30 18:08:16 -0700102 // on some configurations (precision issues?) we need to give some leeway for the implementation
103 var errorTolerance = 0.01;
104
105 leftElem.width(percentStr(leftElemWidthRatio - pads(leftElem)/containerElem.width() - errorTolerance));
106 rightElem.width(percentStr(rightElemWidthRatio - pads(rightElem)/containerElem.width() - errorTolerance));
Kalle Raita30b8a872014-04-03 11:22:31 +0300107
108 currentBarLeftRatio = barLeftRatio;
109 }
110
111 function recompute()
112 {
113 setSizes(currentBarLeftRatio);
114 }
115
116 function move(event)
117 {
118 var barWidthRatio = barElem.outerWidth(true) / containerElem.width();
119 var barLeftRatio = (event.pageX + barLeftOffset) / containerElem.width();
120
121 barLeftRatio = Math.max(barLeftRatio, dragAttrs.leftMinWidthRatio);
122 barLeftRatio = Math.min(barLeftRatio, 1.0 - dragAttrs.rightMinWidthRatio - barWidthRatio);
123
124 setSizes(barLeftRatio);
125 }
126
127 function up()
128 {
129 $document.unbind('mousemove', move);
130 $document.unbind('mouseup', up);
131 }
132
133 angular.element($document).ready(function(){ setSizes(dragAttrs.initialLeftWidthRatio); });
134 // \note Due to precision issues (?) in the precentage widths, we need
135 // to recompute the widths when window is resized.
136 angular.element($window).bind('resize', recompute);
137 }
138})
139
140.directive('fileNameModel', function($compile)
141{
142 return {
143 restrict: 'A',
144 link: function(scope, elem, attrs)
145 {
146 // \note this.value will be a string; JSON.stringify is used to escape possible single quotes in it.
147 elem.attr('onchange', 'angular.element(this).scope().$apply("' + attrs.fileNameModel + ' = " + JSON.stringify(this.value))');
148 }
149 }
150})
151
Kalle Raita5284bc12016-03-25 13:19:09 -0700152.directive('fileModel', function($compile)
153{
154 return {
155 restrict: 'A',
156 link: function(scope, elem, attrs)
157 {
158 elem.attr('onchange', 'angular.element(this).scope().' + attrs.fileModel + ' = this.files;');
159 scope.$watch(attrs.fileModel, function(file) {
160 if (typeof file === "undefined" || file.length == 0)
161 {
162 // File input can only be cleared programmatically.
163 elem.val("");
164 }
165 });
166 }
167 }
168})
169
Kalle Raita30b8a872014-04-03 11:22:31 +0300170.directive('onOffButtonModel', function($compile)
171{
172 return {
173 restrict: 'A',
174 link: function(scope, elem, attrs)
175 {
176 var modelNameStr = JSON.stringify(attrs.onOffButtonModel);
177 elem.attr('onclick', 'angular.element(this).scope().$apply(' + modelNameStr + ' + " = !" + ' + modelNameStr + ');');
178 scope.$watch(attrs.onOffButtonModel, function(active)
179 {
180 if (active)
181 elem.addClass('active');
182 else
183 elem.removeClass('active');
184 });
185 }
186 }
187})
188
189.directive('deDynamicHtml', ['$compile', function($compile)
190{
191 // \todo [petri] is this a better way of doing dynamic html?
192 return {
193 restrict: 'A',
194 replace: true,
195 link: function(scope, elem, attrs)
196 {
197 scope.$watch(attrs.deDynamicHtml, function(html)
198 {
199 if (html)
200 {
201 var compiled = $compile(html.trim())(scope);
202 elem.html('').append(compiled);
203 }
204 });
205 }
206 };
207}])
208
209.directive('rtdbVersionView', ['$compile', 'rtdb', 'rpc', function($compile, rtdb, rpc)
210{
211 return {
212 restrict: 'A',
213 transclude: true,
214 template: '<div ng-if="rtdbVersionView.id !== undefined" ng-transclude></div>',
215 link: function(scope)
216 {
217 scope.rtdbVersionView = { id: undefined };
218 rpc.call('rtdb.NewVersionView', {})
219 .then(function(viewId)
220 {
221 scope.rtdbVersionView.id = viewId;
222 scope.$on('$destroy', function()
223 {
224 scope.rtdbVersionView.id = undefined;
225 rpc.call('rtdb.ReleaseVersionView', { viewId: viewId });
226 });
227 });
228 }
229 };
230}])
231
232;