admin MDL-22806 Added a colourpicker admin setting + supporting JavaScript
authorSam Hemelryk <sam@moodle.com>
Wed, 16 Jun 2010 08:36:06 +0000 (08:36 +0000)
committerSam Hemelryk <sam@moodle.com>
Wed, 16 Jun 2010 08:36:06 +0000 (08:36 +0000)
lang/en/admin.php
lib/adminlib.php
lib/javascript-static.js
pix/.cvsignore [new file with mode: 0644]
pix/i/colourpicker.png [new file with mode: 0644]
theme/base/style/admin.css

index 504f59d..81b6e55 100755 (executable)
@@ -631,6 +631,7 @@ $string['latexpreamble'] = 'LaTeX preamble';
 $string['latexsettings'] = 'LaTeX renderer Settings';
 $string['latinexcelexport'] = 'Excel encoding';
 $string['licensesettings'] = 'License settings';
+$string['loading'] = 'Loading';
 $string['localetext'] = 'Sitewide locale';
 $string['localstringcustomization'] = 'Local string customization';
 $string['location'] = 'Location';
index 35b5402..7f8d29b 100644 (file)
@@ -6940,3 +6940,96 @@ class admin_setting_managewebservicetokens extends admin_setting {
         return highlight($query, $return);
     }
 }
+
+/**
+ * Colour picker
+ * 
+ * @copyright 2010 Sam Hemelryk
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class admin_setting_configcolourpicker extends admin_setting {
+
+    /**
+     * Information for previewing the colour
+     *
+     * @var array|null
+     */
+    protected $previewconfig = null;
+
+    /**
+     *
+     * @param string $name
+     * @param string $visiblename
+     * @param string $description
+     * @param string $defaultsetting
+     * @param array $previewconfig Array('selector'=>'.some .css .selector','style'=>'backgroundColor');
+     */
+    public function __construct($name, $visiblename, $description, $defaultsetting, array $previewconfig=null) {
+        $this->previewconfig = $previewconfig;
+        parent::__construct($name, $visiblename, $description, $defaultsetting);
+    }
+
+    /**
+     * Return the setting
+     *
+     * @return mixed returns config if successful else null
+     */
+    public function get_setting() {
+        return $this->config_read($this->name);
+    }
+
+    /**
+     * Saves the setting
+     * 
+     * @param string $data
+     * @return bool
+     */
+    public function write_setting($data) {
+        $data = $this->validate($data);
+        if ($data === false) {
+            return  get_string('validateerror', 'admin');
+        }
+        return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
+    }
+
+    /**
+     * Validates the colour that was entered by the user
+     *
+     * @param string $data
+     * @return string|false
+     */
+    protected function validate($data) {
+        if (preg_match('/^#?([a-fA-F0-9]{3}){1,2}$/', $data)) {
+            if (strpos($data, '#')!==0) {
+                $data = '#'.$data;
+            }
+            return $data;
+        } else if (preg_match('/^[a-zA-Z]{3, 25}$/')) {
+            return $data;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Generates the HTML for the setting
+     *
+     * @global moodle_page $PAGE
+     * @global core_renderer $OUTPUT
+     * @param string $data
+     * @param string $query
+     */
+    public function output_html($data, $query = '') {
+        global $PAGE, $OUTPUT;
+        $PAGE->requires->js_init_call('M.util.init_colour_picker', array($this->get_id(), $this->previewconfig));
+        $content  = html_writer::start_tag('div', array('class'=>'form-colourpicker defaultsnext'));
+        $content .= html_writer::tag('div', $OUTPUT->pix_icon('i/loading', get_string('loading', 'admin'), 'moodle', array('class'=>'loadingicon')), array('class'=>'admin_colourpicker clearfix'));
+        $content .= html_writer::empty_tag('input', array('type'=>'text','id'=>$this->get_id(), 'name'=>$this->get_full_name(), 'value'=>$this->get_setting(), 'size'=>'12'));
+        if (!empty($this->previewconfig)) {
+            $content .= html_writer::empty_tag('input', array('type'=>'button','id'=>$this->get_id().'_preview', 'value'=>get_string('preview'), 'class'=>'admin_colourpicker_preview'));
+        }
+        $content .= html_writer::end_tag('div');
+        return format_admin_setting($this, $this->visiblename, $content, $this->description, false, '', $this->get_defaultsetting(), $query);
+    }
+
+}
\ No newline at end of file
index 3a346ac..4a81f9e 100644 (file)
@@ -427,6 +427,166 @@ M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname
     }, node);
 }
 
+/**
+ * Initialises a colour picker
+ *
+ * Designed to be used with admin_setting_configcolourpicker although could be used
+ * anywhere, just give a text input an id and insert a div with the class admin_colourpicker
+ * above or below the input (must have the same parent) and then call this with the
+ * id.
+ *
+ * This code was mostly taken from my [Sam Hemelryk] css theme tool available in
+ * contrib/blocks. For better docs refer to that.
+ *
+ * @param {YUI} Y
+ * @param {int} id
+ * @param {object} previewconf
+ */
+M.util.init_colour_picker = function(Y, id, previewconf) {
+    /**
+     * We need node and event-mouseenter
+     */
+    Y.use('node', 'event-mouseenter', function(){
+        /**
+         * The colour picker object
+         */
+        var colourpicker = {
+            box : null,
+            input : null,
+            image : null,
+            preview : null,
+            current : null,
+            eventClick : null,
+            eventMouseEnter : null,
+            eventMouseLeave : null,
+            eventMouseMove : null,
+            width : 300,
+            height :  100,
+            factor : 5,
+            /**
+             * Initalises the colour picker by putting everything together and wiring the events
+             */
+            init : function() {
+                this.input = Y.one('#'+id);
+                this.box = this.input.ancestor().one('.admin_colourpicker');
+                this.image = Y.Node.create('<img alt="" class="colourdialogue" />');
+                this.image.setAttribute('src', M.util.image_url('i/colourpicker', 'moodle'));
+                this.preview = Y.Node.create('<div class="previewcolour"></div>');
+                this.preview.setStyle('width', this.height/2).setStyle('height', this.height/2).setStyle('backgroundColor', this.input.get('value'));
+                this.current = Y.Node.create('<div class="currentcolour"></div>');
+                this.current.setStyle('width', this.height/2).setStyle('height', this.height/2 -1).setStyle('backgroundColor', this.input.get('value'));
+                this.box.setContent('').append(this.image).append(this.preview).append(this.current);
+
+                if (typeof(previewconf) === 'object' && previewconf !== null) {
+                    Y.one('#'+id+'_preview').on('click', function(e){
+                        Y.one(previewconf.selector).setStyle(previewconf.style, this.input.get('value'));
+                    }, this);
+                }
+
+                this.eventClick = this.image.on('click', this.pickColour, this);
+                this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
+            },
+            /**
+             * Starts to follow the mouse once it enter the image
+             */
+            startFollow : function(e) {
+                this.eventMouseEnter.detach();
+                this.eventMouseLeave = Y.on('mouseleave', this.endFollow, this.image, this);
+                this.eventMouseMove = this.image.on('mousemove', function(e){
+                    this.preview.setStyle('backgroundColor', this.determineColour(e));
+                }, this);
+            },
+            /**
+             * Stops following the mouse
+             */
+            endFollow : function(e) {
+                this.eventMouseMove.detach();
+                this.eventMouseLeave.detach();
+                this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
+            },
+            /**
+             * Picks the colour the was clicked on
+             */
+            pickColour : function(e) {
+                var colour = this.determineColour(e);
+                this.input.set('value', colour);
+                this.current.setStyle('backgroundColor', colour);
+            },
+            /**
+             * Calculates the colour fromthe given co-ordinates
+             */
+            determineColour : function(e) {
+                var eventx = Math.floor(e.pageX-e.target.getX());
+                var eventy = Math.floor(e.pageY-e.target.getY());
+
+                var imagewidth = this.width;
+                var imageheight = this.height;
+                var factor = this.factor;
+                var colour = [255,0,0];
+
+                var matrices = [
+                    [  0,  1,  0],
+                    [ -1,  0,  0],
+                    [  0,  0,  1],
+                    [  0, -1,  0],
+                    [  1,  0,  0],
+                    [  0,  0, -1]
+                ];
+
+                var matrixcount = matrices.length;
+                var limit = Math.round(imagewidth/matrixcount);
+                var heightbreak = Math.round(imageheight/2);
+
+                for (var x = 0; x < imagewidth; x++) {
+                    var divisor = Math.floor(x / limit);
+                    var matrix = matrices[divisor];
+
+                    colour[0] += matrix[0]*factor;
+                    colour[1] += matrix[1]*factor;
+                    colour[2] += matrix[2]*factor;
+
+                    if (eventx==x) {
+                        break;
+                    }
+                }
+
+                var pixel = [colour[0], colour[1], colour[2]];
+                if (eventy < heightbreak) {
+                    pixel[0] += Math.floor(((255-pixel[0])/heightbreak) * (heightbreak - eventy));
+                    pixel[1] += Math.floor(((255-pixel[1])/heightbreak) * (heightbreak - eventy));
+                    pixel[2] += Math.floor(((255-pixel[2])/heightbreak) * (heightbreak - eventy));
+                } else if (eventy > heightbreak) {
+                    pixel[0] = Math.floor((imageheight-eventy)*(pixel[0]/heightbreak));
+                    pixel[1] = Math.floor((imageheight-eventy)*(pixel[1]/heightbreak));
+                    pixel[2] = Math.floor((imageheight-eventy)*(pixel[2]/heightbreak));
+                }
+
+                return this.convert_rgb_to_hex(pixel);
+            },
+            /**
+             * Converts an RGB value to Hex
+             */
+            convert_rgb_to_hex : function(rgb) {
+                var hex = '#';
+                var hexchars = "0123456789ABCDEF";
+                for (var i=0; i<3; i++) {
+                    var number = Math.abs(rgb[i]);
+                    if (number == 0 || isNaN(number)) {
+                        hex += '00';
+                    } else {
+                        hex += hexchars.charAt((number-number%16)/16)+hexchars.charAt(number%16);
+                    }
+                }
+                return hex;
+            }
+        }
+        /**
+         * Initialise the colour picker :) Hoorah
+         */
+        colourpicker.init();
+    });
+}
+
 //=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code ===
 
 function popupchecker(msg) {
diff --git a/pix/.cvsignore b/pix/.cvsignore
new file mode 100644 (file)
index 0000000..e43b0f9
--- /dev/null
@@ -0,0 +1 @@
+.DS_Store
diff --git a/pix/i/colourpicker.png b/pix/i/colourpicker.png
new file mode 100644 (file)
index 0000000..d906614
Binary files /dev/null and b/pix/i/colourpicker.png differ
index 2094ffc..5d624f4 100644 (file)
 #adminsettings .form-item .patherror {margin-left: 0.5em;}
 
 #adminthemeselector .selectedtheme td.c0 {border:1px solid;border-right-width:0;}
-#adminthemeselector .selectedtheme td.c1 {border:1px solid;border-left-width:0;}
\ No newline at end of file
+#adminthemeselector .selectedtheme td.c1 {border:1px solid;border-left-width:0;}
+
+.admin_colourpicker,
+.admin_colourpicker_preview {display:none;}
+.jsenabled .admin_colourpicker_preview {display:inline;}
+.jsenabled .admin_colourpicker {display:block;height:102px;width:410px;margin-bottom:10px;}
+.admin_colourpicker .loadingicon {vertical-align:center;margin-left:auto;}
+.admin_colourpicker .colourdialogue {float:left;border:1px solid #000;}
+.admin_colourpicker .previewcolour {border:1px solid #000;margin-left:301px;}
+.admin_colourpicker .currentcolour {border:1px solid #000;margin-left:301px;border-top-width:0;}