HTML preprocessors can make writing HTML more powerful or convenient. For instance, Markdown is designed to be easier to write and read for text documents and you could write a loop in Pug.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. So you don't have access to higher-up elements like the <html>
tag. If you want to add classes there that can affect the whole document, this is the place to do it.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. If you need things in the <head>
of the document, put that code here.
The resource you are linking to is using the 'http' protocol, which may not work when the browser is using https.
CSS preprocessors help make authoring CSS easier. All of them offer things like variables and mixins to provide convenient abstractions.
It's a common practice to apply CSS to a page that styles elements such that they are consistent across all browsers. We offer two of the most popular choices: normalize.css and a reset. Or, choose Neither and nothing will be applied.
To get the best cross-browser support, it is a common practice to apply vendor prefixes to CSS properties and values that require them to work. For instance -webkit-
or -moz-
.
We offer two popular choices: Autoprefixer (which processes your CSS server-side) and -prefix-free (which applies prefixes via a script, client-side).
Any URLs added here will be added as <link>
s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.
You can apply CSS to your Pen from any stylesheet on the web. Just put a URL to it here and we'll apply it, in the order you have them, before the CSS in the Pen itself.
You can also link to another Pen here (use the .css
URL Extension) and we'll pull the CSS from that Pen and include it. If it's using a matching preprocessor, use the appropriate URL Extension and we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
JavaScript preprocessors can help make authoring JavaScript easier and more convenient.
Babel includes JSX processing.
Any URL's added here will be added as <script>
s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.
You can apply a script from anywhere on the web to your Pen. Just put a URL to it here and we'll add it, in the order you have them, before the JavaScript in the Pen itself.
If the script you link to has the file extension of a preprocessor, we'll attempt to process it before applying.
You can also link to another Pen here, and we'll pull the JavaScript from that Pen and include it. If it's using a matching preprocessor, we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
Search for and use JavaScript packages from npm here. By selecting a package, an import
statement will be added to the top of the JavaScript editor for this package.
Using packages here is powered by esm.sh, which makes packages from npm not only available on a CDN, but prepares them for native JavaScript ESM usage.
All packages are different, so refer to their docs for how they work.
If you're using React / ReactDOM, make sure to turn on Babel for the JSX processing.
If active, Pens will autosave every 30 seconds after being saved once.
If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.
If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.
Visit your global Editor Settings.
<h1>A really basic implementation of <a href="https://nytimes.github.io/pourover/">PourOver</a></h1>
<p>Use the filters below to learn about monsters!</p>
<div id="buttons">
<div id="mythology" class="group">
<strong>Mythology:</strong>
<a id="greek" href="#" class="mythology_filter">Greek</a>
<a id="norse" href="#" class="mythology_filter">Norse</a>
</div>
<div id="gender" class="group">
<strong>Gender:</strong>
<a id="male" href="#" class="gender_filter">Male</a>
<a id="female" href="#" class="gender_filter">Female</a>
</div>
<div id="hobbies" class="group">
<strong>Hobbies:</strong>
<a id="riddles" class="hobby_filter" href="#">Riddles</a>
<a id="sitting" class="hobby_filter" href="#">Sitting</a>
<a id="terrorizing" class="hobby_filter" href="#">Terrorizing</a>
<a id="god-killing" class="hobby_filter" href="#">God Killing</a>
<a id="coiling" class="hobby_filter" href="#">Coiling</a>
</div>
<div id="clear-all" class="group">
<a id="clear" href="#" class="mythology_filter">Reset</a>
</div>
</div>
<div id="container"></div>
<script type="text/template" id="feature-grid-template">
<% if (items.length === 0) { %>
<p><strong>No monsters match your search, try again</strong></p>
<% } %>
<ul>
<% for (var i = 0; i < items.length; i++ ) { %>
<% var item = items[i]; %>
<li id="monster-<%= i %>" class="<%= item.mythology %>">
<strong><%= item.name %></strong>
<span>
<%= item.hobbies %>
</span>
</li>
<% } %>
</ul>
</script>
@import url(https://fonts.googleapis.com/css?family=Roboto:400,700);
body {
font-family: 'Roboto', sans-serif;
}
#buttons{
padding: 25px 0;
display:inline-block;
}
#buttons a {
background: #ccc;
text-decoration:none;
color: #333;
padding: 15px;
border-radius: 12px;
display:inline-block;
margin-bottom:5px;
}
#buttons a:hover{
background: #888;
color: #f6f6f6;
}
#buttons a.selected{
background: #333;
color: #f6f6f6;
}
#buttons a#clear{
background: #cff1fa;
}
#buttons .group{
margin:10px 30px 0 0;
float: left;
display:inline-block;
}
#container{
width:100%;
display: inline-block;
clear:both;
}
#footer{
margin: 15px 0;
padding: 0;
border-top: 1px solid #ccc;
clear:both;
font-size:14px;
}
ul {
padding:0;
}
li {
float:left;
display:block;
height:60px;
width: 150px;
text-align:center;
padding-top:25px;
}
li.norse {
background: #0e708c;
color: #fff;
}
li.greek {
background: #47c7ec;
}
li strong{
font-size:16px;
}
li span{
font-size:12px;
display:block;
}
// #Basic PourOver example
// This will cover the creation of a collection and the basic use of filters, views, and sorts
// ###Data
// Let's start with a nice array of data. All PourOver collections *must* be instantiated on
// arrays of hashes like the following, each item a hash of attributes.
var monsters = [{name: "sphinx", mythology: "greek", eyes: 2, sex: "f", hobbies: ["riddles","sitting","being a wonder"]},
{name: "hydra", mythology: "greek", eyes: 18, sex: "m", hobbies: ["coiling","terrorizing","growing"]},
{name: "huldra", mythology: "norse", eyes: 2, sex: "f", hobbies: ["luring","terrorizing"]},
{name: "cyclops", mythology: "greek", eyes: 1, sex: "m", hobbies: ["staring","terrorizing"]},
{name: "fenrir", mythology: "norse", eyes: 2, sex: "m", hobbies: ["growing","god-killing"]},
{name: "medusa", mythology: "greek", eyes: 2, sex: "f", hobbies: ["coiling","staring"]},
{name: "asbolus", mythology: "greek", eyes: 2, sex: "m", hobbies: ["running","divining"]},
{name: "gorgon", mythology: "greek", eyes: 2, sex: "f", hobbies: ["coiling","staring"]},
{name: "mare", mythology: "norse", eyes: 2, sex: "f", hobbies: ["terrorizing","sitting"]},
];
// ###Collection creation
// We create a new PourOver collection by passing the data array into the `PourOver.Collection` constructor.
var collection = new PourOver.Collection(monsters);
// ###Filter creation
// To do anything interesting with collections, we -- almost always -- need to add some filters.
// The most common filter is the exactFilter, a filter that describes an attribute that is satisfied by exactly
// one choice of several possibilities. For example, in the above example, the "mythology" attribute has two possibilities:
// "greek" or "norse". Every item has the value "greek" or "norse" for its mythology.
// *NOTE: `exactFilter`s' names must be identical to the item attribute that they index*
//
// For the most common filter types, such as "exactFilter", PourOver ships with convenience constructors.
// *NOTE: Constructors for preset filters and sorts -- like the ones below -- are not initialized with "new".
// They are simply passed a name and the set of possibilities.*
var mythology_filter = PourOver.makeExactFilter("mythology", ["greek","norse"]);
var gender_filter = PourOver.makeExactFilter("sex", ["m","f"]);
// Now, we will construct the other most-common filter, the `inclusionFilter`. The `inclusionFilter` is similar to the `exactFilter`.
// However, rather than items have a single choice, `inclusionFilter`s describe attributes that can have multiple choices per item.
// *NOTE: `inclusionFilter`s' names must be identical to the item attribute that they index*
var hobbies_filter = PourOver.makeInclusionFilter("hobbies",["riddles","sitting","being a wonder","coiling","terrorizing",
"growing","luring","staring","god-killing"]);
// ###Adding filters
// After constructing our filters, we have to add them to the collection. This will causes the filters to index
// the collection, pre-computing which collection elements satisfy each possibility's predicate. For exact filters, the
// predicate is equality; a collection item satisfies a possibility if it's value is equal to the value of the possibility.
//
// Adding a filter to a collection will also tell the filter to smartly reconstruct itself when items are added to, removed from, or
// updated in the collection.
collection.addFilters([mythology_filter, gender_filter, hobbies_filter]);
// ###Non-stateful (pure) querying of filters
// Now that we have a nice set of filters, we would like to do something interesting: query them and combine the queries into more
// complex queries. PourOver supports both pure and stateful queries. We will cover the former first.
// Here we see that we query `exactFilter`s and `inclusionFilter`s the same way, by passing in the value for which we would like to search.
// *NOTE: Always access the filter through the `collection.filters` object. Do not use the "foo_filter" that you initialized earlier. When
// you add filters to a collection, the filter gets cloned first. Querying the original filters, the pre-added filters, will not work.*
var greek_monsters = collection.filters.mythology.getFn("greek");
var terror_monsters = collection.filters.hobbies.getFn("terrorizing");
// Querying filters returns a `MatchSet`, an objects that wraps a result and can be combined -- AND, OR, NOT -- with other `MatchSets`.
// The boolean combinations will also return `MatchSet`s and can, in turn, be further combined.
var greek_terrors = greek_monsters.and(terror_monsters);
// To get the value of a MatchSet, simply access its `cids` -- read "collection ids" -- property. These cids can be passed to a collection's `get`
// function to transform the cids into the actual objects they represent.
// The value of my_monsters will be:
//
// [{"name":"hydra","mythology":"greek","eyes":18,"sex":"m","hobbies":["coiling","terrorizing","growing"],"cid":1},
// {"name":"cyclops","mythology":"greek","eyes":1,"sex":"m","hobbies":["staring","terrorizing"],"cid":3}]
var my_monsters = collection.get(greek_terrors.cids);
// ###Stateful querying
// Pure queries are always nice but they don't map nicely to the core use case for PourOver:
// modelling UI's in which users query a collection by clicking, sliding, scrubbing and editing
// controls. You will want to remember what their current query is. Otherwise, you'd lose their work!
//
// Stateful queries are very similar to pure queries. You just use the `query` method of a filter instead of the `getFn` method.
// This will store the result of the query on the `current_query` attribute of the filter.
//collection.filters.mythology.query("greek");
//collection.filters.hobbies.query("terrorizing");
var getCurrentMonsters = function(){
var myth_set = collection.filters.mythology.current_query,
hobby_set = collection.filters.hobbies.current_query,
output_set = myth_set.and(hobby_set);
return collection.get(output_set.cids);
}
// Now, whenever a user queries a filter (through some currently undefined UI action), we can just call `getCurrentMonsters()`
// and get the current set of monsters, filtered by mythology and hobbies. By default, an unqueried filter or empty query
// returns a match set representing the entire collection. This means that intersections and unions will work regardless of
// whether or not a query has been made.
//
// But something is missing. We don't want to have to write `getCurrentMonsters` everytime we make an app.
// Also, it would be nice if we could cache the *result* of getCurrentMonsters to speed up subsequent renders.
// Moreover, we don't have any way (yet) to page through results or sort them.
//
// Enter views ...
// ### Views
// In PourOver, a View is used to cache the combination of many queries, paging through and sorting the results.
//
// To construct a View, we pass a name and a collection into the constructor.
//var view = new PourOver.View("default_view", collection);
var MyView = PourOver.View.extend({
template: _.template($("#feature-grid-template").html()),
render: function(){
var items = this.getCurrentItems();
console.log(items);
var html = this.template({items:items});
$('#container').html(html);
}
});
var newsday_view = new MyView( "newsday_view", collection );
newsday_view.on("update",function(){
console.log("updated!");
newsday_view.render();
})
collection.on("change",function(){
console.log("changed!");
newsday_view.render();
})
newsday_view.on("all", function(eventName){
console.log(eventName + ' was triggered!');
});
// Whenever a stateful query occurs, the View will update its cached `MatchSet` with the result of its `selectionFn`. This
// describes how a View should combine the filters on a collection. The default View selectionFn simply intersects all the
// collection's filters together. This should cover the standard use case of a UI in which each control maps to a filter.
// If a user selects "greek" for "mythology" and "terrorizing" for "hobbies", she expects the result to be all monsters which are
// Greek AND terrorize, the intersection of all the filters. (Sex will also be intersected but, since it hasn't been queried in
// our example, it will not affect the result of the intersection)
//
// To get this cached result of a `View`, call the `getCurrentItems` method;
var current_monsters = newsday_view.getCurrentItems();
// ### Conclusion
// This example should cover 90% of use cases for PourOver. For more complicated behavior.
// - the "advanced_views" example will demonstrate sorting, the creation of custom selectionFns, and other arcana
// - the "advanced_filters" example will explain the default filters and demonstrate how to create a new filter type
// - the "buffering" example will demonstate how to use BufferedCollection and BufferedViews to lazily load non-categorical data
// - the "events" example will demonstrate how to plug into the overly complex event system and uses silent updating to optimize queries
// - the "ui" example will demonstrate how to use the PourOver.UI module to simplify the making of UIs
$(function() {
newsday_view.render();
var hobbyArray = []
$("#buttons a").click(function(e) {
e.preventDefault();
var type = $(this).attr("id");
var parent = $(this).parent().attr("id");
if ( parent === "mythology" ) {
if ( $(this).hasClass("selected") ){
$(this).removeClass("selected");
newsday_view.collection.filters.mythology.clearQuery()
} else{
$("a.mythology_filter").removeClass("selected");
if ( type === "greek" ) {
$(this).toggleClass("selected");
newsday_view.collection.filters.mythology.query("greek");
} else if ( type === "norse" ) {
$(this).toggleClass("selected");
newsday_view.collection.filters.mythology.query("norse");
}
}
} else if ( parent === "gender" ) {
if ( $(this).hasClass("selected") ){
$(this).removeClass("selected");
newsday_view.collection.filters.sex.clearQuery()
} else{
$("a.gender_filter").removeClass("selected");
if ( type === "male" ) {
$(this).toggleClass("selected");
newsday_view.collection.filters.sex.query("m");
} else if ( type === "female" ) {
$(this).toggleClass("selected");
newsday_view.collection.filters.sex.query("f");
}
}
} else if ( parent === "hobbies" ) {
if ( $(this).hasClass("selected") ){
$(this).removeClass("selected");
hobbyArray = jQuery.grep(hobbyArray, function(value) {
return value != type;
});
} else{
hobbyArray.push( type );
$(this).toggleClass("selected");
}
newsday_view.collection.filters.hobbies.query( hobbyArray );
}
if ( type === "clear" ){
newsday_view.collection.filters.mythology.clearQuery()
newsday_view.collection.filters.hobbies.clearQuery()
newsday_view.collection.filters.sex.clearQuery()
hobbyArray = []
$("#buttons a").removeClass("selected");
}
});
});
Also see: Tab Triggers