<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>~iany/ Frontend</title><link>https://blog.iany.me/tags/frontend/</link><description>Recent content in Frontend «~iany/»</description><language>en-US</language><managingEditor>me@iany.me (Ian Yang)</managingEditor><webMaster>me@iany.me (Ian Yang)</webMaster><copyright>CC-BY-SA 4.0</copyright><lastBuildDate>Sun, 16 Jun 2013 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.iany.me/tags/frontend/index.xml" rel="self" type="application/rss+xml"/><item><title>Responsive SVG</title><link>https://blog.iany.me/2013/06/responsive-svg/</link><pubDate>Sun, 16 Jun 2013 00:00:00 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2013/06/responsive-svg/</guid><description>&lt;p&gt;SVG can use percentage as unit, but it is often more convenient to use px as unit. Framework such as d3 also uses px internally. However, it is still easy to scale an SVG using attributes &lt;code&gt;viewBox&lt;/code&gt; and &lt;code&gt;preserveAspectRatio&lt;/code&gt; even px is used as unit internally.&lt;/p&gt;
&lt;h2 id="viewbox-and-preserveaspectratio"&gt;viewBox and preserveAspectRatio&lt;/h2&gt;
&lt;p&gt;The attribute &lt;a href="http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute"&gt;viewBox&lt;/a&gt; specifies viewpoint dimension. SVG can be scaled along with the parent container without changing the internal coordinates.&lt;/p&gt;
&lt;p&gt;For example, the SVG below defines a viewpoint with width 100 and
height 100.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;svg viewBox=&amp;quot;0 0 100 100&amp;quot;&amp;gt;
&amp;lt;circle cx=&amp;quot;50&amp;quot; cy=&amp;quot;50&amp;quot; r=&amp;quot;50&amp;quot; fill=&amp;quot;blue&amp;quot;&amp;gt;
&amp;lt;/svg&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The figure below shows the same SVG in different container sizes: 50x50, 100x50, 100x100, 100x200, 200x200 from left to right.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Same SVG in Different Size Containers" class="kg-image" loading="lazy" src="https://blog.iany.me/2013/06/responsive-svg/same-svg-in-different-containers_hu_d41762ac2492bccc.png" srcset="https://blog.iany.me/2013/06/responsive-svg/same-svg-in-different-containers_hu_2bebec1cfcce62d.png 400w, https://blog.iany.me/2013/06/responsive-svg/same-svg-in-different-containers_hu_56a84172c5222a.png 800w, https://blog.iany.me/2013/06/responsive-svg/same-svg-in-different-containers_hu_d41762ac2492bccc.png 1192w" sizes="(max-width: 800px) 100vw, 1192px" /&gt;
&lt;figcaption &gt;Same SVG in Different Size Containers&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Attribute &lt;a href="http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute"&gt;preserveAspectRatio&lt;/a&gt; controls how SVG is scaled according to parent container. There are many combinations, but the most frequently used are default (when the attribute is not specified, or set to &lt;code&gt;xMidYMid meet&lt;/code&gt; explicitly) and &lt;strong&gt;none&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;default, preserve ratio, align center of SVG to the center of the container, scaled up as much as possible but ensure the entire &lt;code&gt;viewBox&lt;/code&gt; is visible within the container.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;none&lt;/code&gt;, do not preserve ratio, scale SVG to fill the whole container.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See the figure similar to the previous one but setting &lt;code&gt;preserveAspectRatio&lt;/code&gt; to &lt;code&gt;none&lt;/code&gt;.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="`preserveAspectRatio=&amp;#34;none&amp;#34;`" class="kg-image" loading="lazy" src="https://blog.iany.me/2013/06/responsive-svg/preserveaspectratio-none_hu_6c121c8a0a82763a.png" srcset="https://blog.iany.me/2013/06/responsive-svg/preserveaspectratio-none_hu_8fd08d7c8cb873dd.png 400w, https://blog.iany.me/2013/06/responsive-svg/preserveaspectratio-none_hu_669370b9ac619e91.png 800w, https://blog.iany.me/2013/06/responsive-svg/preserveaspectratio-none_hu_6c121c8a0a82763a.png 1186w" sizes="(max-width: 800px) 100vw, 1186px" /&gt;
&lt;figcaption &gt;&lt;code&gt;preserveAspectRatio=&amp;quot;none&amp;quot;&lt;/code&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="responsive-svg"&gt;Responsive SVG&lt;/h2&gt;
&lt;p&gt;First add following CSS:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-css"&gt;svg {
width: 100%;
height: 100%;
}
svg * {
vector-effect: non-scaling-stroke;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first rule ensures SVG always fills parent container. And the second one sets a default &lt;code&gt;vector-effect&lt;/code&gt; that when SVG is scaled, leave stroke width untouched.&lt;/p&gt;
&lt;p&gt;If the SVG does not need to preserve ratio, set &lt;code&gt;preserveAspectRatio&lt;/code&gt; to &lt;code&gt;none&lt;/code&gt;, and set the height of the container. For example, the SVG below has a fixed height &lt;code&gt;50px&lt;/code&gt;, and is half width of the whole page.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;div style=&amp;quot;width:50%;height:50px;&amp;quot;&amp;gt;
&amp;lt;svg viewBox=&amp;quot;0 0 100 100&amp;quot; preserveAspectRatio=&amp;quot;none&amp;quot;&amp;gt;
&amp;lt;circle cx=&amp;quot;50&amp;quot; cy=&amp;quot;50&amp;quot; r=&amp;quot;50&amp;quot; fill=&amp;quot;blue&amp;quot;&amp;gt;
&amp;lt;/svg&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;figure&gt;
&lt;div style="width:50%;height:50px;"&gt;
&lt;svg viewBox="0 0 100 100" style="width:100%;height:100%" preserveAspectRatio="none"&gt;
&lt;circle cx="50" cy="50" r="50" fill="blue" /&gt;
&lt;/svg&gt;
&lt;/div&gt;
&lt;figcaption&gt;Fixed With, Horizontal scalable&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If the SVG must preserve ratio, then we need a bit of JavaScript to change container height when window is resized.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;$.fn.preserveAspectRatio = function() {
this.each(function() {
var $this = $(this);
var ratioSpec = $this.attr('data-preserveAspectRatio').split(':');
var ratio = parseInt(ratioSpec[1], 10) / parseInt(ratioSpec[0], 10);
$this.height($this.width() * ratio);
});
};
var preserveAspectRatio = function() {
$('[data-preserveAspectRatio]').preserveAspectRatio();
};
$(function() {
preserveAspectRatio();
$(window).on('resize', preserveAspectRatio);
// Better to wrap preserveAspectRatio in underscore debounce or jquery-throttle-debounce.
// $(window).on('resize', _.debounce(preserveAspectRatio, 200));
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then set &lt;code&gt;data-preserveAspectRatio&lt;/code&gt; on any container that needs preserve aspect ratio. Following example sets the width height ratio to &lt;code&gt;1:1&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;div style=&amp;quot;width:50%;&amp;quot; data-preserveAspectRatio=&amp;quot;1:1&amp;quot;&amp;gt;
&amp;lt;svg viewBox=&amp;quot;0 0 100 100&amp;quot; preserveAspectRatio=&amp;quot;none&amp;quot;&amp;gt;
&amp;lt;circle cx=&amp;quot;50&amp;quot; cy=&amp;quot;50&amp;quot; r=&amp;quot;50&amp;quot; fill=&amp;quot;blue&amp;quot;&amp;gt;
&amp;lt;/svg&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See the demo in this &lt;a href="https://codepen.io/doitian/pen/njmzrR"&gt;pen&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="references"&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://web.archive.org/web/20171114160758/http://meloncholy.com/blog/making-responsive-svg-graphs/"&gt;Making responsive SVG graphs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.w3.org/TR/SVG/coords.html"&gt;SVG Coordinate Systems, Transformations and Units&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/css/">CSS</category><category domain="https://blog.iany.me/tags/frontend/">Frontend</category><category domain="https://blog.iany.me/tags/svg/">SVG</category></item><item><title>Rails Compound Input</title><link>https://blog.iany.me/2013/01/rails-compound-input/</link><pubDate>Sat, 19 Jan 2013 00:00:00 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2013/01/rails-compound-input/</guid><description>&lt;p&gt;When I implement
&lt;a href="https://github.com/19wu/19wu/pull/160"&gt;time input feature&lt;/a&gt; for
&lt;a href="https://github.com/saberma/19wu"&gt;19wu&lt;/a&gt; (an open source ticket sale system), I
want to split the datetime into date and time parts, so JavaScript date picker
and time picker can be used. This post introduces two methods I found.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Compound datetime input" class="kg-image" loading="lazy" src="https://blog.iany.me/2013/01/rails-compound-input/compound_datetime_input_hu_294b76325e647193.png" /&gt;
&lt;figcaption &gt;Compound datetime input&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;composed_of&lt;/code&gt; utilizes &lt;code&gt;assign_multiparameter_attributes&lt;/code&gt; trick like
&lt;code&gt;datetime_select&lt;/code&gt;, and &lt;code&gt;fields_for&lt;/code&gt; mocks an association.&lt;/p&gt;
&lt;p&gt;The github repository &lt;a href="https://github.com/doitian/rails-compound-input-demo"&gt;doitian/rails-compound-input-demo&lt;/a&gt; contains demos for both methods.&lt;/p&gt;
&lt;h2 id="composed_of"&gt;composed_of&lt;/h2&gt;
&lt;p&gt;Rails has built-in compound inputs &lt;code&gt;date_select&lt;/code&gt;, &lt;code&gt;time_select&lt;/code&gt; and
&lt;code&gt;datetime_select&lt;/code&gt;. They use trick that if parameter name has parentheses, they
will be used in the attribute class constructor (see
&lt;a href="http://apidock.com/rails/ActiveRecord/AttributeAssignment/assign_multiparameter_attributes" title="ActiveRecord::AttributeAssignment#assign_multiparameter_attributes"&gt;assign_multiparameter_attributes&lt;/a&gt;). However, datetime attribute is of type
DateTime, which accepts 6 parameters year, month, day, hour, minute and
second.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://api.rubyonrails.org/classes/ActiveRecord/Aggregations/ClassMethods.html#method-i-composed_of" title="ActiveRecord::Aggregations.composed_of"&gt;composed_of&lt;/a&gt; can be used here to represent the datetime attribute using
value object, which class constructor accepts date and time strings. See
&lt;code&gt;CompoundDatetime#initialize&lt;/code&gt; below.&lt;/p&gt;
&lt;p&gt;※ &lt;a href="https://github.com/doitian/rails-compound-input-demo/blob/master/composed_of/app/models/compound_datetime.rb"&gt;compound_datetime.rb&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class CompoundDatetime
def self.from_datetime(datetime)
new.tap do |result|
result.datetime = datetime
end
end
attr_accessor :datetime
# Accepts date and time string. The form just need to submit params
#
# - compound_beginning_time(1s) for date
# - compound_beginning_time(2s) for time
def initialize(date = nil, time = nil)
if date.present?
@datetime = Time.zone.parse([date.presence, time.presence || ''].join(' '))
end
end
def date
@datetime.strftime('%Y-%m-%d') if @datetime
end
def time
@datetime.strftime('%H:%M') if @datetime
end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then setup the mapping in model &lt;code&gt;Event&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;※ &lt;a href="https://github.com/doitian/rails-compound-input-demo/blob/master/composed_of/app/models/event.rb"&gt;event.rb&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class Event &amp;lt; ActiveRecord::Base
attr_accessible :beginning_time, :title
attr_accessible :compound_beginning_time
composed_of :compound_beginning_time, {
:class_name =&amp;gt; 'CompoundDatetime',
:mapping =&amp;gt; [ %w(beginning_time datetime) ],
:converter =&amp;gt; Proc.new { |datetime| CompoundDatetime.from_datetime(datetime) }
}
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The form view just needs set correct name:&lt;/p&gt;
&lt;p&gt;※ &lt;a href="https://github.com/doitian/rails-compound-input-demo/blob/master/composed_of/app/views/events/_form.html.erb"&gt;events/_form.html.erb&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&amp;quot;field&amp;quot;&amp;gt;
&amp;lt;%= f.label :compound_beginning_time, 'Begining Time' %&amp;gt;&amp;lt;BR /&amp;gt;
&amp;lt;%= text_field_tag 'event[compound_beginning_time(1s)]', @event.compound_beginning_time.date, :placeholder =&amp;gt; 'yyyy-mm-dd' %&amp;gt;
&amp;lt;%= text_field_tag 'event[compound_beginning_time(2s)]', @event.compound_beginning_time.time, :placeholder =&amp;gt; 'HH:MM' %&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="fields_for"&gt;fields_for&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;fields_for&lt;/code&gt; is usually used to embed associations in form. However, all it
required was a method to return the named attribute, and then a &lt;code&gt;&amp;lt;field&amp;gt;_attributes=&lt;/code&gt;
writer to interpret the hash on the other side. See the section &amp;ldquo;Nested Attributes Examples&amp;rdquo; in the
&lt;a href="https://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for"&gt;&lt;code&gt;fields_for&lt;/code&gt; API document&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First create &lt;code&gt;CompoundDatetime&lt;/code&gt; class which exposes date and time
fields. &lt;code&gt;assign_attributes&lt;/code&gt; handles the hash params passed from form. Method
&lt;code&gt;persisted?&lt;/code&gt; is required to quiet &lt;code&gt;NoMethodError&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;※ &lt;a href="https://github.com/doitian/rails-compound-input-demo/blob/master/fields_for/app/models/compound_datetime.rb"&gt;compound_datetime.rb&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class CompoundDatetime
attr_accessor :datetime
def initialize(datetime)
@datetime = datetime
end
# accepts hash like:
#
# {
# 'date' =&amp;gt; '2012-12-20',
# 'time' =&amp;gt; '20:30'
# }
def assign_attributes(hash)
if hash[:date].present?
@datetime = Time.zone.parse([hash[:date].presence, hash[:time].presence || ''].join(' '))
end
self
end
def date
@datetime.strftime('%Y-%m-%d') if @datetime
end
def time
@datetime.strftime('%H:%M') if @datetime
end
def persisted?; false; end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The model just delegates the named attribute and &lt;code&gt;&amp;lt;field&amp;gt;_attributes=&lt;/code&gt; method to &lt;code&gt;CompoundDatetime&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;※ &lt;a href="https://github.com/doitian/rails-compound-input-demo/blob/master/fields_for/app/models/event.rb"&gt;event.rb&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class Event &amp;lt; ActiveRecord::Base
attr_accessible :beginning_time, :title
attr_accessible :compound_beginning_time_attributes
def compound_beginning_time
CompoundDatetime.new(beginning_time)
end
def compound_beginning_time_attributes=(attributes)
self.beginning_time = compound_beginning_time.assign_attributes(attributes).datetime
end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The form view uses &lt;code&gt;fields_for&lt;/code&gt; helper to nest fields of &lt;code&gt;compound_begining_time&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;※ &lt;a href="https://github.com/doitian/rails-compound-input-demo/blob/master/fields_for/app/views/events/_form.html.erb"&gt;events/_form.html.erb&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&amp;quot;field&amp;quot;&amp;gt;
&amp;lt;%= f.label :beginning_time %&amp;gt;&amp;lt;br /&amp;gt;
&amp;lt;%= f.fields_for :compound_beginning_time do |fields| %&amp;gt;
&amp;lt;%= fields.text_field :date, :placeholder =&amp;gt; 'yyyy-mm-dd' %&amp;gt;
&amp;lt;%= fields.text_field :time, :placeholder =&amp;gt; 'HH:MM' %&amp;gt;
&amp;lt;% end %&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="reference"&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.alexrothenberg.com/2009/05/21/how-to-use-dates-in-rails-when-your.html"&gt;Alex Rothenberg - How to use dates in Rails when your database stores a string&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for"&gt;&lt;code&gt;fields_for&lt;/code&gt; API document&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://guides.rubyonrails.org/form_helpers.html"&gt;Ruby on Rails Guides: Rails Form helpers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://apidock.com/rails/ActiveRecord/AttributeAssignment/assign_multiparameter_attributes" title="ActiveRecord::AttributeAssignment#assign_multiparameter_attributes"&gt;assign_multiparameter_attributes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://api.rubyonrails.org/classes/ActiveRecord/Aggregations/ClassMethods.html#method-i-composed_of" title="ActiveRecord::Aggregations.composed_of"&gt;composed_of&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/form/">Form</category><category domain="https://blog.iany.me/tags/frontend/">Frontend</category><category domain="https://blog.iany.me/tags/rails/">Rails</category></item></channel></rss>