Version 1
[yaffs-website] / web / core / modules / color / color.js
1 /**
2  * @file
3  * Attaches the behaviors for the Color module.
4  */
5
6 (function ($, Drupal) {
7
8   'use strict';
9
10   /**
11    * Displays farbtastic color selector and initialize color administration UI.
12    *
13    * @type {Drupal~behavior}
14    *
15    * @prop {Drupal~behaviorAttach} attach
16    *   Attach color selection behavior to relevant context.
17    */
18   Drupal.behaviors.color = {
19     attach: function (context, settings) {
20       var i;
21       var j;
22       var colors;
23       // This behavior attaches by ID, so is only valid once on a page.
24       var form = $(context).find('#system-theme-settings .color-form').once('color');
25       if (form.length === 0) {
26         return;
27       }
28       var inputs = [];
29       var hooks = [];
30       var locks = [];
31       var focused = null;
32
33       // Add Farbtastic.
34       $('<div class="color-placeholder"></div>').once('color').prependTo(form);
35       var farb = $.farbtastic('.color-placeholder');
36
37       // Decode reference colors to HSL.
38       var reference = settings.color.reference;
39       for (i in reference) {
40         if (reference.hasOwnProperty(i)) {
41           reference[i] = farb.RGBToHSL(farb.unpack(reference[i]));
42         }
43       }
44
45       // Build a preview.
46       var height = [];
47       var width = [];
48       // Loop through all defined gradients.
49       for (i in settings.gradients) {
50         if (settings.gradients.hasOwnProperty(i)) {
51           // Add element to display the gradient.
52           $('.color-preview').once('color').append('<div id="gradient-' + i + '"></div>');
53           var gradient = $('.color-preview #gradient-' + i);
54           // Add height of current gradient to the list (divided by 10).
55           height.push(parseInt(gradient.css('height'), 10) / 10);
56           // Add width of current gradient to the list (divided by 10).
57           width.push(parseInt(gradient.css('width'), 10) / 10);
58           // Add rows (or columns for horizontal gradients).
59           // Each gradient line should have a height (or width for horizontal
60           // gradients) of 10px (because we divided the height/width by 10
61           // above).
62           for (j = 0; j < (settings.gradients[i].direction === 'vertical' ? height[i] : width[i]); ++j) {
63             gradient.append('<div class="gradient-line"></div>');
64           }
65         }
66       }
67
68       // Set up colorScheme selector.
69       form.find('#edit-scheme').on('change', function () {
70         var schemes = settings.color.schemes;
71         var colorScheme = this.options[this.selectedIndex].value;
72         if (colorScheme !== '' && schemes[colorScheme]) {
73           // Get colors of active scheme.
74           colors = schemes[colorScheme];
75           for (var fieldName in colors) {
76             if (colors.hasOwnProperty(fieldName)) {
77               callback($('#edit-palette-' + fieldName), colors[fieldName], false, true);
78             }
79           }
80           preview();
81         }
82       });
83
84       /**
85        * Renders the preview.
86        */
87       function preview() {
88         Drupal.color.callback(context, settings, form, farb, height, width);
89       }
90
91       /**
92        * Shifts a given color, using a reference pair (ref in HSL).
93        *
94        * This algorithm ensures relative ordering on the saturation and
95        * luminance axes is preserved, and performs a simple hue shift.
96        *
97        * It is also symmetrical. If: shift_color(c, a, b) === d, then
98        * shift_color(d, b, a) === c.
99        *
100        * @function Drupal.color~shift_color
101        *
102        * @param {string} given
103        *   A hex color code to shift.
104        * @param {Array.<number>} ref1
105        *   First HSL color reference.
106        * @param {Array.<number>} ref2
107        *   Second HSL color reference.
108        *
109        * @return {string}
110        *   A hex color, shifted.
111        */
112       function shift_color(given, ref1, ref2) {
113         var d;
114         // Convert to HSL.
115         given = farb.RGBToHSL(farb.unpack(given));
116
117         // Hue: apply delta.
118         given[0] += ref2[0] - ref1[0];
119
120         // Saturation: interpolate.
121         if (ref1[1] === 0 || ref2[1] === 0) {
122           given[1] = ref2[1];
123         }
124         else {
125           d = ref1[1] / ref2[1];
126           if (d > 1) {
127             given[1] /= d;
128           }
129           else {
130             given[1] = 1 - (1 - given[1]) * d;
131           }
132         }
133
134         // Luminance: interpolate.
135         if (ref1[2] === 0 || ref2[2] === 0) {
136           given[2] = ref2[2];
137         }
138         else {
139           d = ref1[2] / ref2[2];
140           if (d > 1) {
141             given[2] /= d;
142           }
143           else {
144             given[2] = 1 - (1 - given[2]) * d;
145           }
146         }
147
148         return farb.pack(farb.HSLToRGB(given));
149       }
150
151       /**
152        * Callback for Farbtastic when a new color is chosen.
153        *
154        * @param {HTMLElement} input
155        *   The input element where the color is chosen.
156        * @param {string} color
157        *   The color that was chosen through the input.
158        * @param {bool} propagate
159        *   Whether or not to propagate the color to a locked pair value
160        * @param {bool} colorScheme
161        *   Flag to indicate if the user is using a color scheme when changing
162        *   the color.
163        */
164       function callback(input, color, propagate, colorScheme) {
165         var matched;
166         // Set background/foreground colors.
167         $(input).css({
168           backgroundColor: color,
169           color: farb.RGBToHSL(farb.unpack(color))[2] > 0.5 ? '#000' : '#fff'
170         });
171
172         // Change input value.
173         if ($(input).val() && $(input).val() !== color) {
174           $(input).val(color);
175
176           // Update locked values.
177           if (propagate) {
178             i = input.i;
179             for (j = i + 1; ; ++j) {
180               if (!locks[j - 1] || $(locks[j - 1]).is('.is-unlocked')) {
181                 break;
182               }
183               matched = shift_color(color, reference[input.key], reference[inputs[j].key]);
184               callback(inputs[j], matched, false);
185             }
186             for (j = i - 1; ; --j) {
187               if (!locks[j] || $(locks[j]).is('.is-unlocked')) {
188                 break;
189               }
190               matched = shift_color(color, reference[input.key], reference[inputs[j].key]);
191               callback(inputs[j], matched, false);
192             }
193
194             // Update preview.
195             preview();
196           }
197
198           // Reset colorScheme selector.
199           if (!colorScheme) {
200             resetScheme();
201           }
202         }
203       }
204
205       /**
206        * Resets the color scheme selector.
207        */
208       function resetScheme() {
209         form.find('#edit-scheme').each(function () {
210           this.selectedIndex = this.options.length - 1;
211         });
212       }
213
214       /**
215        * Focuses Farbtastic on a particular field.
216        *
217        * @param {jQuery.Event} e
218        *   The focus event on the field.
219        */
220       function focus(e) {
221         var input = e.target;
222         // Remove old bindings.
223         if (focused) {
224           $(focused).off('keyup', farb.updateValue)
225             .off('keyup', preview).off('keyup', resetScheme)
226             .parent().removeClass('item-selected');
227         }
228
229         // Add new bindings.
230         focused = input;
231         farb.linkTo(function (color) { callback(input, color, true, false); });
232         farb.setColor(input.value);
233         $(focused).on('keyup', farb.updateValue).on('keyup', preview).on('keyup', resetScheme)
234           .parent().addClass('item-selected');
235       }
236
237       // Initialize color fields.
238       form.find('.js-color-palette input.form-text')
239         .each(function () {
240           // Extract palette field name.
241           this.key = this.id.substring(13);
242
243           // Link to color picker temporarily to initialize.
244           farb.linkTo(function () {}).setColor('#000').linkTo(this);
245
246           // Add lock.
247           var i = inputs.length;
248           if (inputs.length) {
249             var toggleClick = true;
250             var lock = $('<button class="color-palette__lock">' + Drupal.t('Unlock') + '</button>').on('click', function (e) {
251               e.preventDefault();
252               if (toggleClick) {
253                 $(this).addClass('is-unlocked').html(Drupal.t('Lock'));
254                 $(hooks[i - 1]).attr('class',
255                   locks[i - 2] && $(locks[i - 2]).is(':not(.is-unlocked)') ? 'color-palette__hook is-up' : 'color-palette__hook'
256                 );
257                 $(hooks[i]).attr('class',
258                   locks[i] && $(locks[i]).is(':not(.is-unlocked)') ? 'color-palette__hook is-down' : 'color-palette__hook'
259                 );
260               }
261               else {
262                 $(this).removeClass('is-unlocked').html(Drupal.t('Unlock'));
263                 $(hooks[i - 1]).attr('class',
264                   locks[i - 2] && $(locks[i - 2]).is(':not(.is-unlocked)') ? 'color-palette__hook is-both' : 'color-palette__hook is-down'
265                 );
266                 $(hooks[i]).attr('class',
267                   locks[i] && $(locks[i]).is(':not(.is-unlocked)') ? 'color-palette__hook is-both' : 'color-palette__hook is-up'
268                 );
269               }
270               toggleClick = !toggleClick;
271             });
272             $(this).after(lock);
273             locks.push(lock);
274           }
275
276           // Add hook.
277           var hook = $('<div class="color-palette__hook"></div>');
278           $(this).after(hook);
279           hooks.push(hook);
280
281           $(this).parent().find('.color-palette__lock').trigger('click');
282           this.i = i;
283           inputs.push(this);
284         })
285         .on('focus', focus);
286
287       form.find('.js-color-palette label');
288
289       // Focus first color.
290       inputs[0].focus();
291
292       // Render preview.
293       preview();
294     }
295   };
296
297 })(jQuery, Drupal);