Kalle Raita | 30b8a87 | 2014-04-03 11:22:31 +0300 | [diff] [blame] | 1 | // 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 | |
| 19 | angular.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öyry | 9ad8b18 | 2015-06-30 18:08:16 -0700 | [diff] [blame] | 102 | // 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 Raita | 30b8a87 | 2014-04-03 11:22:31 +0300 | [diff] [blame] | 107 | |
| 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 Raita | 5284bc1 | 2016-03-25 13:19:09 -0700 | [diff] [blame^] | 152 | .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 Raita | 30b8a87 | 2014-04-03 11:22:31 +0300 | [diff] [blame] | 170 | .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 | ; |