Browse Source

material design

windhamdavid 10 years ago
parent
commit
6ead473fcb
2 changed files with 376 additions and 0 deletions
  1. 167 0
      js/material-ripples.js
  2. 209 0
      js/material.js

+ 167 - 0
js/material-ripples.js

@@ -0,0 +1,167 @@
+/* Copyright 2014+, Federico Zivolo, LICENSE at https://github.com/FezVrasta/bootstrap-material-design/blob/master/LICENSE.md */
+/* globals jQuery, navigator */
+
+(function($) {
+
+  // Detect if the browser supports transitions
+  $.support.transition = (function(){
+    var thisBody = document.body || document.documentElement,
+        thisStyle = thisBody.style,
+        support = (
+          thisStyle.transition !== undefined ||
+          thisStyle.WebkitTransition !== undefined ||
+          thisStyle.MozTransition !== undefined ||
+          thisStyle.MsTransition !== undefined ||
+          thisStyle.OTransition !== undefined
+        );
+    return support;
+  })();
+
+  $.ripples = function(options) {
+
+    // Default options
+    var defaultOptions = {
+      "target": ".btn:not(.btn-link), .card-image, .navbar a:not(.withoutripple), .nav-tabs a:not(.withoutripple), .withripple"
+    };
+
+
+    function isTouch() {
+      return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
+    }
+
+
+    // Fade out the ripple and then destroy it
+    function rippleOut(ripple) {
+
+      // Unbind events from ripple
+      ripple.off();
+
+      // Start the out animation
+      if ($.support.transition) {
+        ripple.addClass("ripple-out");
+      } else {
+        ripple.animate({
+          "opacity": 0
+        }, 100, function() {
+          ripple.trigger("transitionend");
+        });
+      }
+
+      // This function is called when the transition "out" ends
+      ripple.on("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd", function(){
+        ripple.remove();
+      });
+
+    }
+
+    // Apply custom options
+    options = $.extend(defaultOptions, options);
+
+
+    $(document)
+    .on("mousedown touchstart", options.target, function(e) {
+      if (isTouch() && e.type == "mousedown") {
+        return false;
+      }
+
+      var element = $(this);
+
+      // If the ripple wrapper does not exists, create it
+      if (!$(this).find(".ripple-wrapper").length) {
+        $(this).append("<div class=ripple-wrapper></div>");
+      }
+
+      var wrapper = $(this).find(".ripple-wrapper");
+
+
+      var wrapperOffset = wrapper.offset(),
+          relX,
+          relY;
+      if (!isTouch()) {
+        // Get the mouse position relative to the ripple wrapper
+        relX = e.pageX - wrapperOffset.left;
+        relY = e.pageY - wrapperOffset.top;
+      } else {
+        // Make sure the user is using only one finger and then get the touch position relative to the ripple wrapper
+        e = e.originalEvent;
+        if (e.touches.length === 1) {
+          relX = e.touches[0].pageX - wrapperOffset.left;
+          relY = e.touches[0].pageY - wrapperOffset.top;
+        } else {
+          return;
+        }
+      }
+
+      // Meet the new ripple
+      var ripple = $("<div></div>");
+
+      // Add to it the ripple class
+      ripple.addClass("ripple");
+
+      // Position it in the right place
+      ripple.css({"left": relX, "top": relY});
+
+      // Set the background color of the ripple
+      ripple.css({"background-color": window.getComputedStyle($(this)[0]).color});
+
+      // Spawn it
+      wrapper.append(ripple);
+
+      // Make sure the ripple has the styles applied (ugly hack but it works)
+      (function() { return window.getComputedStyle(ripple[0]).opacity; })();
+
+      // Set the new size
+      var size = (Math.max($(this).outerWidth(), $(this).outerHeight()) / ripple.outerWidth()) * 2.5;
+
+
+      // Decide if use CSS transitions or jQuery transitions
+      if ($.support.transition) {
+        // Start the transition
+        ripple.css({
+          "-ms-transform": "scale(" + size + ")",
+          "-moz-transform": "scale(" + size + ")",
+          "-webkit-transform": "scale(" + size + ")",
+          "transform": "scale(" + size + ")"
+        });
+        ripple.addClass("ripple-on");
+        ripple.data("animating", "on");
+        ripple.data("mousedown", "on");
+      } else {
+        // Start the transition
+        ripple.animate({
+          "width": Math.max($(this).outerWidth(), $(this).outerHeight()) * 2,
+          "height": Math.max($(this).outerWidth(), $(this).outerHeight()) * 2,
+          "margin-left": Math.max($(this).outerWidth(), $(this).outerHeight()) * -1,
+          "margin-top": Math.max($(this).outerWidth(), $(this).outerHeight()) * -1,
+          "opacity": 0.2
+        }, 500, function() {
+          ripple.trigger("transitionend");
+        });
+      }
+
+      // This function is called when the transition "on" ends
+      setTimeout(function() {
+        ripple.data("animating", "off");
+        if (ripple.data("mousedown") == "off") {
+          rippleOut(ripple);
+        }
+      }, 500);
+
+      // On mouseup or on mouseleave, set the mousedown flag to "off" and try to destroy the ripple
+      element.on("mouseup mouseleave", function() {
+        ripple.data("mousedown", "off");
+        // If the transition "on" is finished then we can destroy the ripple with transition "out"
+        if (ripple.data("animating") == "off") {
+          rippleOut(ripple);
+        }
+      });
+
+    });
+
+  };
+
+  $.fn.ripples = function() {
+    $.ripples({"target": $(this)});
+  };
+
+})(jQuery);

+ 209 - 0
js/material.js

@@ -0,0 +1,209 @@
+/* globals jQuery */
+
+(function($) {
+  // Selector to select only not already processed elements
+  $.expr[":"].notmdproc = function(obj){
+    if ($(obj).data("mdproc")) {
+      return false;
+    } else {
+      return true;
+    }
+  };
+
+  function _isChar(evt) {
+    if (typeof evt.which == "undefined") {
+      return true;
+    } else if (typeof evt.which == "number" && evt.which > 0) {
+      return !evt.ctrlKey && !evt.metaKey && !evt.altKey && evt.which != 8;
+    }
+    return false;
+  }
+
+  $.material =  {
+    "options": {
+      // These options set what will be started by $.material.init()
+      "input": true,
+      "ripples": true,
+      "checkbox": true,
+      "togglebutton": true,
+      "radio": true,
+      "arrive": true,
+      "autofill": true,
+
+      "withRipples": [
+        ".btn:not(.btn-link)",
+        ".card-image",
+        ".navbar a:not(.withoutripple)",
+        ".dropdown-menu a",
+        ".nav-tabs a:not(.withoutripple)",
+        ".withripple"
+      ].join(","),
+      "inputElements": "input.form-control, textarea.form-control, select.form-control",
+      "checkboxElements": ".checkbox > label > input[type=checkbox]",
+      "togglebuttonElements": ".togglebutton > label > input[type=checkbox]",
+      "radioElements": ".radio > label > input[type=radio]"
+    },
+    "checkbox": function(selector) {
+      // Add fake-checkbox to material checkboxes
+      $((selector) ? selector : this.options.checkboxElements)
+      .filter(":notmdproc")
+      .data("mdproc", true)
+      .after("<span class=ripple></span><span class=check></span>");
+    },
+    "togglebutton": function(selector) {
+      // Add fake-checkbox to material checkboxes
+      $((selector) ? selector : this.options.togglebuttonElements)
+      .filter(":notmdproc")
+      .data("mdproc", true)
+      .after("<span class=toggle></span>");
+    },
+    "radio": function(selector) {
+      // Add fake-radio to material radios
+      $((selector) ? selector : this.options.radioElements)
+      .filter(":notmdproc")
+      .data("mdproc", true)
+      .after("<span class=circle></span><span class=check></span>");
+    },
+    "input": function(selector) {
+      $((selector) ? selector : this.options.inputElements)
+      .filter(":notmdproc")
+      .data("mdproc", true)
+      .each( function() {
+        var $this = $(this);
+        $this.wrap("<div class=form-control-wrapper></div>");
+        $this.after("<span class=material-input></span>");
+
+        // Add floating label if required
+        if ($this.hasClass("floating-label")) {
+          var placeholder = $this.attr("placeholder");
+          $this.attr("placeholder", null).removeClass("floating-label");
+          $this.after("<div class=floating-label>" + placeholder + "</div>");
+        }
+
+        // Add hint label if required
+        if ($this.attr("data-hint")) {
+          $this.after("<div class=hint>" + $this.attr("data-hint") + "</div>");
+        }
+
+        // Set as empty if is empty (damn I must improve this...)
+        if ($this.val() === null || $this.val() == "undefined" || $this.val() === "") {
+          $this.addClass("empty");
+        }
+
+        // Support for file input
+        if ($this.parent().next().is("[type=file]")) {
+          $this.parent().addClass("fileinput");
+          var $input = $this.parent().next().detach();
+          $this.after($input);
+        }
+      });
+
+      $(document)
+      .on("change", ".checkbox input[type=checkbox]", function() { $(this).blur(); })
+      .on("keydown paste", ".form-control", function(e) {
+        if(_isChar(e)) {
+          $(this).removeClass("empty");
+        }
+      })
+      .on("keyup change", ".form-control", function() {
+        var $this = $(this);
+        if($this.val() === "") {
+          $this.addClass("empty");
+        } else {
+          $this.removeClass("empty");
+        }
+      })
+      .on("focus", ".form-control-wrapper.fileinput", function() {
+        $(this).find("input").addClass("focus");
+      })
+      .on("blur", ".form-control-wrapper.fileinput", function() {
+        $(this).find("input").removeClass("focus");
+      })
+      .on("change", ".form-control-wrapper.fileinput [type=file]", function() {
+        var value = "";
+        $.each($(this)[0].files, function(i, file) {
+          console.log(file);
+          value += file.name + ", ";
+        });
+        value = value.substring(0, value.length - 2);
+        if (value) {
+          $(this).prev().removeClass("empty");
+        } else {
+          $(this).prev().addClass("empty");
+        }
+        $(this).prev().val(value);
+      });
+    },
+    "ripples": function(selector) {
+      $.ripples({"target": (selector) ? selector : this.options.withRipples});
+    },
+    "autofill": function() {
+
+      // This part of code will detect autofill when the page is loading (username and password inputs for example)
+      var loading = setInterval(function() {
+        $("input[type!=checkbox]").each(function() {
+          if ($(this).val() && $(this).val() !== $(this).attr("value")) {
+            $(this).trigger("change");
+          }
+        });
+      }, 100);
+
+      // After 10 seconds we are quite sure all the needed inputs are autofilled then we can stop checking them
+      setTimeout(function() {
+        clearInterval(loading);
+      }, 10000);
+      // Now we just listen on inputs of the focused form (because user can select from the autofill dropdown only when the input has focus)
+      var focused;
+      $(document)
+      .on("focus", "input", function() {
+        var $inputs = $(this).parents("form").find("input").not("[type=file]");
+        focused = setInterval(function() {
+          $inputs.each(function() {
+            if ($(this).val() !== $(this).attr("value")) {
+              $(this).trigger("change");
+            }
+          });
+        }, 100);
+      })
+      .on("blur", "input", function() {
+        clearInterval(focused);
+      });
+    },
+    "init": function() {
+      if ($.ripples && this.options.ripples) {
+        this.ripples();
+      }
+      if (this.options.input) {
+        this.input();
+      }
+      if (this.options.checkbox) {
+        this.checkbox();
+      }
+      if (this.options.togglebutton) {
+        this.togglebutton();
+      }
+      if (this.options.radio) {
+        this.radio();
+      }
+      if (this.options.autofill) {
+        this.autofill();
+      }
+
+      if (document.arrive && this.options.arrive) {
+        $(document).arrive(this.options.inputElements, function() {
+          $.material.input($(this));
+        });
+        $(document).arrive(this.options.checkboxElements, function() {
+          $.material.checkbox($(this));
+        });
+        $(document).arrive(this.options.radioElements, function() {
+          $.material.radio($(this));
+        });
+        $(document).arrive(this.options.togglebuttonElements, function() {
+          $.material.togglebutton($(this));
+        });
+      }
+    }
+  };
+
+})(jQuery);