| About Dailyrotten |
|
Feedback
If you like this page, hate it, or would like to suggest changes,
<EMAIL_ADDRESS>is listening. This page
is experimental and somewhat different from what we usually offer, so please
do send your feedback.
Adding Stories
If you are a rotten newshound and you would like to suggest a story,
visit our submit page to
send us the URL. Stories that are accepted will have your name credited.
Posting Comments
In order to post comments to stories, you will have to
create an account.
Accounts will generally be validated within 24 hours, and of
course will be disabled if there is abuse.
Viewership
On the first full day this page went live, 100,000 people visited this Rotten
News page. Of course that was the day after the WTC attacks, so that number is
somewhat distorted. Right now this page gets between 50,000 and 75,000 people
every day.
Linking
We encourage everyone to link to this page; if you like it, others probably
will as well.
Other Rotten
Rotten
Gaping Maw
Celebrity Morgue
Mugshots
Rotten Store
Rate My Poo
Rate My Kitten
Rate My Finger
Penny Postcards
Phallic
Boners
Home
Make Dailyrotten your start page
|
|
|
August 19, 2002
Naked bloodsoaked men charged
WKMG-TV 6 Orlando | Submitted by: Rio
"Police have charged four men and a teenager with animal cruelty after
finding them naked and doused in the blood of a dead goat with dismembered
heads of other animals strewn in the yard. Investigators believe the men
were practicing religious rituals commonly associated with Santeria, said
Tampa Police spokeswoman Katie Hughes. Neighbors contacted police
Saturday after seeing five men naked and running around the back yard
of a Tampa home covered in blood, according to a police report. Police found
the dead goat, which appeared to have its throat slashed, and a number of
headless chickens, doves and pigeons."
|
|
Read article...
|
|
Woman set on fire whilst giving birth
Sydney Morning Herald | Submitted by: Rebecca
"A woman was set on fire during a caesarean delivery at the maternity unit
of Waitakere Hospital in New Zealand at the weekend. The fire left the woman,
who was giving birth, with burns to the lower part of her body.
The Waitemata District Health Board said the fire was a freak accident."
|
|
Read article...
|
|
Prodigy kills speedfreak mice
Sydney Morning Herald | Submitted by: Bungknckle
Bored scientists come up with a pearler of an experiment. "Lets get
some mice, inject themem with crystal-meth and turn up the stereo". The
results? "They appeared to "jiggle backwards and forwards" as the music
pounded in their ears." ... and then, they died.
|
|
Read article...
|
|
Suitcase full of cocaine, under $1
Wire Services | Submitted by: LucrezaBorgia
"A Canadian man paid less than a dollar for a used suitcase at an auction
house only to find when he got home that it was crammed with nearly 11 pounds
of cocaine with a street value of US$180,000, police said on Friday...
He contacted police after he opened the bag to find a hard substance separated
into 158 small bags, later determined to be crack for street sale."
|
|
Read article...
|
|
Referee bashed at kid friendly match
New Zealand Herald | Submitted by: Spookbabe
"He was attacked, allegedly by supporters of the Ellerslie under-10s
team, which was winning easily when he called a halt to the game,
ruling it had become too violent. His 10-year-old son, Jesse, and the
other children burst into tears as they watched him being kicked by
several people as he lay curled up on the ground."
|
|
Read article...
|
|
Stepdaughter from hell
San Francisco Chronicle | Submitted by: Bo Orly
He was a retired judge and state senator in his twilight years, living on
his ranch and receiving more than thirteen grand a month in retirement money.
 You might think this kind of detail would be embarrasing to
the, er, widow of the departed 91 year old, but she stood up and gave a speech
about it at the funeral. Expect several lawyers to make large amounts of money
sorting this one out.
|
|
Read article...
|
|
Admission of Guilt, Inc.
FAIR | Submitted by: Crazy Quagga
What if a major multimedia corporation CEO spoke honestly about the
company's nefarious schemes and crooked deals? Media watchdog FAIR takes a
stab as to what it might sound like. (The ultimate irony of course is,
that this is essentially a commercial plug for a book.)
|
|
Read article...
|
|
Philly puppet protesters still being pummelled
Citypaper | Submitted by: Crazy Quagga
Woes of the 2000 RNC protesters: "A property receipt from the Philadelphia
Police Department describes other items seized from the warehouse during the
RNC: several balloons containing an 'unknown liquid substance' and a blue
plastic canteen filled with 'unknown liquid.' A Philadelphia crime lab report
dated Aug. 4, 2000, concludes that this mysterious fluid was ... water. (So
much for the poisonous brew police warned Bridge about.)"
|
|
Read article...
|
|
Beer goggle science
Reuters | Submitted by: Crampon
"British scientists have found even modest amounts of alcohol will make
the opposite sex appear better-looking... The study of 120 male and female
students found drinking up to four units of alcohol, about two pints of
beer or four glasses of wine, increased the perceived attractiveness of
members of the opposite sex by about 25 percent."
|
|
Read article...
|
|
Serial cat killer pissing off Italians
Washington Post | Submitted by: Son of Benway
"The alarm is out in Prenestina, a rambling, anonymous southern Rome
neighborhood, for a serial killer who has dispatched at least six victims
with a kick to the ribs or strychnine. The most recent victim, named
Lorenzo, died of fractured ribs with nary a farewell meow... "There's a
single hand behind this terrible chain of violence. His luck is running
out," said police inspector Antonio Biscozzi. "There's already a suspect."
|
|
Read article...
|
|
Death by stoning for adultery
ITV | Submitted by: Son of Benway
"A court in Nigeria has rejected a woman's appeal against death by stoning
for having a child outside marriage. The judgment was made under Sharia,
religious law recognised by Muslims. Clutching her baby daughter, the woman,
Amina Lawal Kurami, 31, burst into tears as the judge delivered the ruling. The
judge at Funtua, in Nigeria's northern Katsina state. said the sentence would
not be carried out until she weans her child."
 "He just
jumped right in front of these cars going 50 and 60 mph," said Sgt. Jim
Collis.  When that didn't work, he
jumped on the guardrail of a nearby bridge and threatened to jump.
|
|
Read article...
|
|
Cover your Blowhole
Honolulu Advertiser | Submitted by: Incubus
"At the blowhole, he leaned over the opening, forming a bridge with his
body. About 30 seconds later, a geyser of water shot through the hole,
catching him square in the chest and sending him 8 to 12 feet into the air.
He somersaulted and disappeared head-first into the blowhole. The victim's
mother now wants the Hawaii Department of Land and Natural Resources
to put a grate over the entrance to prevent others from suffering the
same fate as her son." A grate will not hold back Darwin.
|
|
Read article...
|
|
Face-Clawing Monster terrifies Indian state
ABC News | Submitted by: Oskar Pryor
"Victims report being scratched by something flashing blue, red or
green that strikes only at faces and only at night. The creature has been
dubbed "Muhnochwa," or "face-clawing monster." ... Police have said that at
least two people have died and scores have been injured in alleged
"Muhnochwa" attacks that began a month ago at the start of the monsoon."
|
|
Read article...
|
|
Who flew those 911 planes?
 It is a
feature that is standard to all of them, all 757's and 767's can do it. The
purpose for this is if there is a problem with the pilots, Norad can fly the
planes to safe destinations via remote. Only in this flight mode can those
craft exceed their software limits and perform to their actual physical limits
because a pre existing emergency situation is assumed if this mode of flight is
used. Terrorists in fact did not fly those planes, it is totally and completely
impossible for those planes to have been flown in such a manner from the
cockpit. Those are commuter aircraft, not F-16's and their software knows it."
|
|
Read article...
|
|
Authorities raise stink over Amish outhouse
Dayton Daily News | Submitted by: Idle Hands
"Dan and Lena Miller said they would rather go to jail than install a
watertight, concrete tank under the new outhouse they built for their son,
who lives with his wife on an adjacent farm. A Knox County judge ruled
last week that they must make the $200 update by Sept. 16. The outhouse
is next to a stream, creating the health risk that human waste can leak
into the area’s water supply... The Millers use decomposed waste from
the outhouse to fertilize wheat, barley, corn and oats on their farm."
|
|
Read article...
|
|
Attention K-mart coppers: You are being overzealous
Houston Chronicle | Submitted by: Ann Onymous
"We went to use the restroom at Kmart and to buy a Scrunchi (hair band), and when we came back to our car, cops were coming in (the parking lot) and they tied our hands," said Brandi Ratliff, 18, who said she was a straight-A student at Waller High School and never had any problems with the law...
Ratliff said that even though she and two friends told police they had just come out of the Kmart, all three were arrested and spent the night in jail."
|
|
Read article...
|
|
Segway != Ginger?
silicon.com | Submitted by: Soylent
"Ginger chat site administrator 'Spliff' has examined Kemper's book proposal
in some detail, and has taken a fine-tooth comb to the patents Kamen has
registered, and found significant evidence to suggest Segway is not the thing
which blew Bezos and Jobs away... This has reignited all the original
speculation that the invention is some kind of personal hovercraft."
|
|
Read article...
|
|
|
|
|


{{template "header.html" .}}
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
WireGuard is a registered trademark of Jason A. Donenfeld


MENU
CHOLERA
BY THE STAR
explore
about
methodology
page1
page1
page1
methodology
page1
page1
page1
methodology
page1
page1
page1
methodology
page1
page1
page1


Overview
Cosmic Explorer is a next-generation observatory concept that will greatly deepen and clarify humanity’s gravitational-wave view of the cosmos. It is the planned U.S. contribution to the global next-generation ground-based gravitational-wave observatory network. The design concept for Cosmic Explorer features two facilities, one 40 km on a side and one 20 km on a side, each housing a single L-shaped detector.
Cosmic Explorer together with other future detectors, including LISA and the Einstein Telescope, will be able to determine the nature of the densest matter in the universe; reveal the universe’s binary black hole and neutron star populations throughout cosmic time; provide an independent probe of the history of the expanding universe; explore warped spacetime with unprecedented fidelity; and expand our knowledge of how massive stars live, die, and create the matter we see today.
With its spectacular sensitivity, Cosmic Explorer will see gravitational-wave sources across the universe. Sources that are barely detectable by Advanced LIGO, Advanced Virgo, and Kagra will be resolved with incredible precision. The resulting explosion in the number of detected sources — up to millions per year — and the fidelity of observations will have wide-ranging impacts in physics and astronomy. By peering deep into the gravitational-wave sky, Cosmic Explorer will present a unique opportunity for new and unexpected discoveries.
See the Horizon Study for more information on Cosmic Explorer science, design and technology.
Layout
Like the current generation of observatories, Cosmic Explorer features an L-shaped geometry and houses a single interferometer. Each Cosmic Explorer facility will have two 40 km (20 km for the second site) ultrahigh-vacuum beam tubes, roughly 1 m in diameter, built in an L-shape on the surface of flat and seismically quiet land in the United States. This conventional design leverages decades of experience with current gravitational-wave detectors to ensure project success.
Scale
Cosmic Explorer’s 40 km arms (20 km for the second site), 10 (5) times longer than Advanced LIGO’s, will increase the amplitude of the observed signals with effectively no increase in the noise. Although there are areas of detector technology where improvements will lead to increases in the sensitivity and bandwidth of the instruments, the dominant improvement will come from significantly increasing the arm length.
Technology
Cosmic Explorer will be built with the technology developed for the A+ upgrade to Advanced LIGO, scaled up to a 40 km and 20km observatories with correspondingly better sensitivity. Planned upgrades to Cosmic Explorer will allow it to realize a full order of magnitude sensitivity improvement beyond Advanced LIGO.
Science
Seeing Black Holes Merge throughout Cosmic Time
Advanced LIGO's observations of black holes merging in 2015, GW150914, gave humanity a glimpse of a previously unknown side of the Universe. Cosmic Explorer can detect merging stellar-mass black holes at redshifts of up to z ∼ 20. This immense reach will reveal for the first time the complete population of stellar-mass black holes, starting from an epoch when the universe was still assembling its first stars. Cosmic Explorer will detect hundreds of thousands of black-hole mergers each year, measuring their masses and spins. These observations will reveal the black-hole merger rate, the underlying star formation rate, how both have changed throughout cosmic time, and how both are correlated with galaxy evolution.
Investigating the Densest Matter in the Universe
Neutron stars are made of the densest stable matter in the universe. Six decades after their discovery, we still do not understand how matter behaves at the pressures and densities found in neutron-star interiors. The first neutron star merger, GW170817, was found in gravitational waves and observed across the electromagnetic spectrum. Observations of the mergers and remnants of neutron stars with gravitational waves and light illuminate unknown physics in the state of matter at ultra-high densities. Cosmic Explorer will capture these mergers with the precision needed to understand dense neutron-rich matter.
Exploring the Gravitational Wave Frontier
Cosmic Explorer might see other spectacular sources, the detection of any one of which would be revolutionary. Examples include the core collapse of a massive star in the Milky Way or Magellanic Clouds, emissions from mountains or quakes in pulsars, and glitches in magnetars in our galaxy. Cosmic Explorer might also see gravitational waves from forms of dark and exotic matter around black holes or in the cores of neutron stars, the mergers of primordial black holes formed in the early universe, or gravitational-wave emission from cosmic (super)strings.
Impact
Broader Context
Cosmic Explorer is a visionary project, not just for the advances in gravitational-wave science, but also as a model for how science can be more impactful when it is done in interdisciplinary, socially connected ways. The CE observatories will reshape the field of gravitational-wave astrophysics and impact the future of current early career researchers. The Decadal Survey on Astronomy and Astrophysics 2020 (Astro2020) Panel on the State of the Profession and Societal Impacts identified seven essential goals for the State of the Profession in the next decade. The large physical scale of the CE observatories, and the potential social, cultural, and economic impacts of their construction and presence, means that the success of Cosmic Explorer will depend on developing ongoing, positive partnerships with local and Indigenous communities. The CE Project and Consortium aim to address these goals through our Equity, Diversity, and Inclusion plan and Indigenous Partnerships Program.
Indigenous Partnership
The work of the Indigenous Partnership Program will be to respectfully collaborate with Indigenous communities to develop mutually beneficial relationships and resources; appropriately integrate Indigenous science; demonstrate how scientific facilities can foster positive interactions with Indigenous communities; and, create frameworks for facilities construction that integrate the interests, priorities, and goals of Indigenous communities from initial conception through decommissioning. This work will set a precedent for protocol around facilities construction and the scientific approach that places community interests and culture at the center of process from conception; promoting transparent, trustworthy, and engaged science; and, building capacity within Indigenous and local communities to engage with the scientific community in ways that are respectful of culture and traditional knowledge and uphold community rights, interests, values, and consent.
Equity, Diversity and Inclusion
A strength of the Cosmic Explorer community is the involvement of university faculty, staff, and students who contribute to Equity, Diversity, and Inclusion (EDI). Central coordination of EDI efforts and analysis of demographic data by the CE leadership provides structure and resources to the CE community to facilitate and assess EDI activities, and to identify effective practices. The University of Washington Center for Evaluation and Research for STEM Equity (CERSE) uses demographic data to advise on strategic planning and organizational development to improve equity and broaden representation. CE is connected to the broader community through the Multimessenger Diversity Network (MDN), Gravitational Wave Early Career Scientists (GWECS), and the GW Allies to facilitate the sharing of resources and best practices.
News
US 3G
Dawn VI Meeting on Next Generation Observatories Endorses Cosmic Explorer, December, 2021
The Dawn VI Meeting on Next Generation Observatories brought together over 200 physicists and astronomers in October 2021 to discuss the next-generation ground-based gravitational-wave detectors. The report from this meeting, published in December 2021, has endorsed Cosmic Explorer stating:
"There was a consensus that Cosmic Explorer is a concept that can deliver the promised science. A strong endorsement of Cosmic Explorer, as described in the CE Horizon Study, is a primary outcome of DAWN VI."
To endorse the DAWN VI report, or for any questions, send email to David Shoemaker.
by Duncan Brown on December 24, 2021
Astro2020 Decadal Survey: A Resounding Endorsement for Cosmic Explorer, November, 2021
The National Academies' 2020 Decadal Survey on Astronomy and Astrophysics resoundingly endorsed Cosmic Explorer, stating that a next-generation gravitational-wave observatory in the United States is "central to achieving the science vision laid out in the survey’s roadmap." The Cosmic Explorer Consortium appreciates the support we have received from the Astro 2020 survey, which states that:
"Gravitational wave astrophysics is one of the most exciting frontiers in science. One of the survey’s key priorities is the opening of new windows on the dynamic universe, with gravitational wave detection at the forefront. The continued growth in sensitivity of current-generation facilities, such as LIGO, through phased upgrades and planning the next-generation observatory, such as Cosmic Explorer, is essential. This will require investment in technology development now. The survey committee strongly endorses gravitational wave observations as central to many crucial science objectives."
by Matthew Evans on November 5, 2021
Cosmic Explorer Horizon Study, July, 2021
The Cosmic Explorer Horizon Study is now public! This is a near-final draft of the report, with work on-going to incorporte community input.
by Matthew Evans on July 21, 2021
Cosmic Explorer meeting, October 26–30, 2020
The first Cosmic Explorer meeting will be held online, October 26–30, to discuss the science case and technical design for next-generation, ground-based gravitational-wave detectors. Learn more at the conference webpage. The deadline to register is October 20.
by Evan Hall on October 1, 2020
Study Detectability of Supernovae by Cosmic Explorer published by Physical Review
Syracuse University graduate student Varun Srivastava's study on the detectability of supernovae with Cosmic Explorer has been published in Physical Review D and is available at https://journals.aps.org/prd/abstract/10.1103/PhysRevD.100.043026.
by Duncan Brown on September 16, 2019
Read moreCosmic Explorer White Paper for Astro2020 Decadal Survey Submitted
Together with members of the LIGO lab, the Cosmic Explorer team wrote and submitted “Cosmic Explorer: The U.S. Contribution to Gravitational-Wave Astronomy beyond LIGO,” an Astro2020 Decadal Survey APC ground-based technology development white paper. The white paper is available online at https://arxiv.org/abs/1907.04833.
by Geoffrey Lovelace on August 5, 2019
Read moreUS 3G Effort Funded by NSF
The National Science Foundation has awarded a collaborative grant to 5 US institutions to study the science case for an international network of next-generation gravitational-wave detectors, and the potential avenues for construction of a 3G detector in the United States [award 1836814].
The grant started in August, 2018, and the collaboration is working with the GWIC 3G effort to develop a comprehensive vision of 3G science. Development of a cost model for Cosmic Explorer is also an integral part of the work, which is currently incorporating the results of the NSF Workshop on Large Ultrahigh Vacuum Systems for Frontier Scientific Research Instrumentation [award 1846124, final report].
by Matthew Evans on March 13, 2019
Read more


version {{.Version}}
IP ADDRESS:
MASK:
GATEWAY:
SERVER ADDRESS:
DHCP:
BACK
DELETE ALL
SAVE
© 2021 Petr Jahoda


Shuai Palm Zhou is a Master student who majors in Computer Science in Jack Baskin School at University of California, Santa Cruz. In 2016, Shuai obtained the Bachelor of Science degree in Software Technology and Application and graduated with first class honors from Faculty of Information Technology at Macau University of Science and Technology. From Feb. 2016 to May 2017, Under the supervision of Dr.Jun Wan, Shuai worked as a research intern in National Laboratory of Pattern Recognition at Institute of Automation, Chinese Academy of Science.
Shuai has research interests on computer vision with special interest on object detection, tracking or research related to facial attributes and human activities or gesture.
Science fiction and sci-fi movie lover.


So that is another year at Hooting Yard done and dusted. Many thanks to my readers and listeners, particularly to those who help me keep the wolf from the door by sending donations, subscribing, and/or buying my paperbacks from Lulu.
I was going to list here a few personal favourites from the Year of Our Lord MMXII, but then it occurred to me that, really, the best thing you can do is to reread the entire year's worth of postages, all 550 of them (not including this one), at one prolonged sitting, your brow furrowed in concentration, while outside the wind howls and the rain pours down. After all, “the proper study of Mankind is Hooting Yard”, as Alexander Pope said (attrib.). The attrib., by the by, was made by me, just a moment ago.
I began the year with the demented plan to write a thousandish-word essay every day, and persisted until mid-November, when the scheme ganged agley due to my (alas, temporary) membership of the international jetset. For the Year of Our Lord MMXIII, I have devised a different project, still somewhat blurry in my bonce, the first fruits of which will, I hope, appear here tomorrow.
Meanwhile, at the turning of the year it is time to take stock, so here is a snap of the actor Nigel Stock (1919–1986), who played Dr Watson in the BBC's 1960s Sherlock Holmes adaptations, as well as popping up in Brighton Rock (John Boulting, 1947) and The Great Escape (John Sturges, 1963), among many other films.


Add & Edit metadata per file
Any fields and values can be used here, but some have some special meaning.
Using a field call LINK will add the contents of this field as a hyperlink to the metabotnik. Each image with a LINK field then gets a small rectangle in the upper left corner which displays the LINK and lets the user click on the link to navigate there.
close


This class is used to make an object for Comment.

Id of the person who added the comment.

Person who added comment.
# File projects/model/Comment.rb, line 148 def getAddedPerson return @addedPerson end
Get the comment content.
Comment content.
# File projects/model/Comment.rb, line 48 def getContent return @content end
Get the created time.
Created time.
# File projects/model/Comment.rb, line 68 def getCreatedTime return @createdTime end
Get the created time format.
Created time format.
# File projects/model/Comment.rb, line 88 def getCreatedTimeFormat return @createdTimeFormat end
Get the created time long.
Created time.
# File projects/model/Comment.rb, line 108 def getCreatedTimeLong return @createdTimeLong end
Get the comment id.
Comment id.
# File projects/model/Comment.rb, line 28 def getId return @id end
Get the comment posted date.
Date at which comment is posted.
# File projects/model/Comment.rb, line 229 def getPostDate return @postDate end
Get the comment posted date format.
Date format of the posted comment.

The date on which the comment is posted.

Id of the person who posted the comment.

Person who posted the comment.

Id of the person who updated the comment.
# File projects/model/Comment.rb, line 169 def getUpdatedBy return @updatedBy end
Set the id for the comment added person.
ID of the person who added the comment.
# File projects/model/Comment.rb, line 118 def setAddedBy(addedBy) @addedBy = addedBy end
Set the comment added person.
Person who added comment.
# File projects/model/Comment.rb, line 138 def setAddedPerson(addedPerson) @addedPerson = addedPerson end
Set the comment content.
Content for the comment.
# File projects/model/Comment.rb, line 38 def setContent(content) @content = content end
Set the created time.
Created time for the comment.
# File projects/model/Comment.rb, line 58 def setCreatedTime(createdTime) @createdTime = createdTime end
Set the created time format.
Created time format for the comment.
# File projects/model/Comment.rb, line 78 def setCreatedTimeFormat(createdTimeFormat) @createdTimeFormat = createdTimeFormat end
Set the created time long.
Created time for the comment.
# File projects/model/Comment.rb, line 98 def setCreatedTimeLong(createdTimeLong) @createdTimeLong = createdTimeLong end
Set the comment id.
ID of the comment.
# File projects/model/Comment.rb, line 18 def setId(id) @id = id end
Set the comment posted date.
Date at which comment is posted.
# File projects/model/Comment.rb, line 219 def setPostDate(postDate) @postDate = postDate end
Set the comment posted date format.
Date format of the posted comment.
# File projects/model/Comment.rb, line 239 def setPostDateFormat(postDateFormat) @postDateFormat = postDateFormat end
Set the comment posted date long.
Was was the comment posted(date).
# File projects/model/Comment.rb, line 259 def setPostDateLong(postDateLong) @postDateLong = postDateLong end
Set the comment posted person id.
Id of the person who posted the comment.
# File projects/model/Comment.rb, line 179 def setPostedBy(postedBy) @postedBy = postedBy end
Set the comment posted person.
Person who posted the comment.
# File projects/model/Comment.rb, line 199 def setPostedPerson(postedPerson) @postedPerson = postedPerson end
Set the comment updated person id.
ID of the person who updated the comment.
# File projects/model/Comment.rb, line 159 def setUpdatedBy(updatedBy) @updatedBy = updatedBy end
Convert the Comment object into HashMap.
HashMap object.
# File projects/model/Comment.rb, line 279 def toParamMAP requestBody = Hash.new if content != nil requestBody["content"] = content end return requestBody end


--- date: 2010-08-22T00:00:00Z published: true title: MercatorNet - Human = animal = robot - World's Most Dangerous Idea url: /2010/08/22/mercatornet-human-animal-robot-world-s-most-dangerous-idea/ ---
Wrestling with difficult questions is routine work for ethicists. But some are much more difficult than others. MercatorNet’s question, “What did I believe was presently the world’s most dangerous idea?”, falls in the former category. My response, “The idea that there is nothing special about being human and, therefore, humans do not deserve “special respect”, as compared with other animals or even robots”, might seem anodyne and a “cop out”, but I’d like to try to convince you otherwise.


JAN
13
Mary Birtday
Fri 13th Jan, 7:00PM iHub, Nairobi, Kenya
JAN
13
Mary Birtday
Fri 13th Jan, 7:00PM iHub, Nairobi, Kenya
JAN
13
Mary Birthday
Fri 13th Jan, 7:00PM iHub, Nairobi, Kenya
JAN
13
Mary Birtday
Fri 13th Jan, 7:00PM iHub, Nairobi, Kenya


Image optimization can often provide some of the largest performance improvements for any website or app and ImgBot offers one of the most turn-key solutions out there. For projects hosted on GitHub, image optimizations are literally just a few clicks away.
With ImgBot already installed in over 100,000 projects and prominently featured in the GitHub Marketplace, I get to help make the internet a little bit faster every single day.
ClearlyDefined is an incubator project from the Open Source Initiative that is on a mission to help all open source projects thrive in today's software development ecosystem. We believe lack of clarity around licenses and security vulnerabilities reduces engagement, which leads to fewer users, fewer contributors, and in turn, a smaller community.
As a core contributor on the project I get to work with node.js workers and services running in Docker containers in multiple clouds and a whole community of stakeholders and contributors.
As a core developer from 2013 to 2018 I got to take part in the explosive growth happening at that time. Helping Azure grow from 4 services to the 100+ we have now, we also grew our scrappy 4 person team to an organization of more than 50 members.
Top 5 reasons this project rocked every day:
While disposable analytics may be a micro-niche, it can be a lot of fun. Venmorld reads from the pubic Venmo payment feed and streams the data right to the browser. The emoji’s are extracted from the message and charted on screen.
After being frustrated by needing to get on a laptop just to check on a few poison queues I set out to make myself a tool just for me. Fast forward a bit and I realized I was starting to build out a full feature set and decided to release an app.
The app was available in the Google Play store for a while until it was taken down for trademark infringement (the word Azure). We peaked at a bit over 40 monthly active users before the app was retired.
Based on the skit from the infamous Seattle version of Saturday Night Live, Almost Live, this game asks you the important question. Is it on Pike or is it on Pine?
It shows you different bars and restaurants and you guess if it's on Pike street or Pine street. It's a fun way to get more acquainted with where things are on Capitol Hill and Downtown.


| Struts 2 > Home > Tutorials > Step by Step > TutorialLesson04-01 |
When using JSP to render the views, you can choose to access the action's data using scriptlets or tags. Tags are the recommended approach.
Action data can be accessed through an object called Value Stack. The example below does the same thing as the result page of lesson 3's second example (Supplying Data to the Action), but using scriptlets:
<%@ page import="com.opensymphony.xwork.util.OgnlValueStack" %> <html> <head> <title>WebWork Tutorial - Lesson 4.1 - Lesson 3's example modified</title> </head> <body> <% OgnlValueStack stack = (OgnlValueStack)request.getAttribute("webwork.valueStack"); out.write("Hello, " + stack.findValue("person")); %> </body> </html>
WebWork tags, however, are recommended over scriptlets. For instance, <ww:property /> tags do exactly what the scriptlet above does, with a cleaner syntax and also handles the case where the Value Stack doesn't exist.
We've already showed in lesson 3's example how to access an action's property using tags. This section describes and exemplifies the use of the WebWork Tag Library, which can be divided in seven categories:
| <ww:property /> | Gets the value of a result attribute. If the value isn't given, the top of the stack will be returned. |
| <ww:push /> | Pushes a value onto the Value Stack. |
| <ww:param /> | Sets a parent tag's parameter. This tag is used only inside another tag to set the value of some property of the parent tag. |
| <ww:set /> | Sets the value of an object in the Value Stack to a scope (page, stack, application, session). If the value is not given, the top of the stack is used. If the scope is not given, the default scope of "webwork" is used. |
| <ww:url /> | Builds an encoded URL. |
EXAMPLE NEEDED.
| <ww:action /> | Executes an Action from within the context of a taglib. The body of the tag is used to display the results of the action invocation. |
| <ww:bean /> | Creates a JavaBean, instantiate its properties and place it in the ActionContext for later use. |
| <ww:include /> | Includes another page or action. |
EXAMPLE NEEDED.
This if-else set of tags works just like if-else scriptlets.
| <ww:if /> | Conditional execution path. That is, evaluates the tag body if a boolean expression is true. |
| <ww:else /> | Negative execution path for the if tag. That is, if the preceeding conditional tag's boolean expression evaluated to false, then evaluate this tag's body. |
| <ww:elseif /> | Negative conditional execution path for the if tag. That is, if the preceeding conditional tag's boolean expression evaluated to false and if this tag's boolean expression evaluates to true, then evaluate this tag's body. |
EXAMPLE NEEDED.
| <ww:iterator /> | Iterates over a collection. |
| <ww:generator /> | Generates iterators. |
| <ww:append /> | Appends several iterators. |
| <ww:subset /> | Gets a subset of an iterator. |
| <ww:merge /> | Merges several iterators into one. |
| <ww:sort /> | Sorts an iterator. |
EXAMPLE NEEDED.
The UI tags wrap generic HTML controls while providing tight integration with the core framework. The tags have been designed to minimize the amount of logic in compiled code and delegate the actual rendering of HTML to a template system. The UI tags attempt to cover the most common scenarios, while providing a Component Tag for creating custom components. The UI tags also provide built-in support for displaying inline error messages.
There is a separate lesson about WebWork UI Tags which explains in detail how they work, how you could cusomize their appearance through the use of templates, how to create custom components, etc.
| <ww:audio /> | ??? |
| <ww:prompt /> | ??? |
| <ww:filled /> | ??? |
| <ww:log /> | ??? |
Volunteers needed to write this part.
| <ww:text /> | Prints out an internationalized string. |
| <ww:i18n /> | Places a resource bundle on the Value Stack, for access by the text tag. |


Created by Diego Lendoiro / @sinvitaminas
“Docker is an open platform for developing, shipping, and running applications. Docker is designed to deliver your applications faster. With Docker you can separate your applications from your infrastructure AND treat your infrastructure like a managed application. Docker helps you ship code faster, test faster, deploy faster, and shorten the cycle between writing code and running code.”
You can think of it as:
Or, as most people say, you can think of it as:
Docker is about Packaging, isolating and running
“F*CK, my VirtualBox is too slow!!”
“you need my config file to run it”
“I'm tired of mocking my services”
Containers are not persistent by default
Persistence must be achieved through VOLUMES
Containers get random IP addresses
Automatic bridge between host and container
Multi container apps need LINKING
Basic "bash" templates to automate docker image builds
Perfect for CVS different environments or stages
Uploadable to docker hub for sharing
FROM debian:latest
MAINTAINER<EMAIL_ADDRESS>RUN apt-get update && \
apt-get install -y --force-yes \
git \
nginx-full
EXPOSE 80
CMD [ "nginx", "-g", "daemon off;" ]


| ${value}% Chance to be Used | 6 8 10 12 14 16 17 18 19 20 |
| Launches ${value} Projectile(s) | 1 |
| ${value} Bleeding Damage over ${duration} Second(s) | min: [24.0, 36.0, 49.5, 64.5, 81.0, 99.0, 118.5, 139.5, 162.0, 187.5] Bleeding Damage over min: 3.0 Second(s) |
| +${value}% increased Pierce Ratio | 14 18 22 26 30 34 38 42 46 50 |
| MaxLevel | 6 |
| UltimateLevel | 10 |


THtmlViewer component
property MaxVertical: integer;
MaxVertical gives the height in pixels of the document as it is currently formatted. This height may be used to adjust THtmlViewer's height to just fit a small document.
Note that MaxVertical will ordinarily change if the document width changes.


News story
New historical research released reveals how employers’ relationships with apprenticeships have changed over the last 100 years.
Employers have always considered apprenticeships to be a beneficial method of vocational training: 100 years ago this was concentrated mainly around artisan trades, but as the number of larger employers grew in the twentieth century, this evolved to newer metalworking industries like engineering and shipbuilding.
The Top 10 trades and sectors for apprenticeships in 1914 and 2014 show how engineering and construction were popular choices both then and now. In contrast, the most popular trade in 1914 was dressmaking, whereas today health and social care tops the list. Today the growth sector for apprenticeships include: the digital sector, banking and professional services such as tax consultancy.
The research reveals that whichever vocation chosen, most apprentices in 1914 started work aged 15-17, compared with 19-24 today. Further, one hundred years ago, women made up 22% of apprentices, a figure that has significantly increased to 55% today.
As they do today, in 1914 employers offered a mixture of on-the-job training and formal education. However, the research also reveals some major differences: apprentices were ‘indentured’ (legally required) to work for an employer for a number of years and had to pay a fee to their employer to cover the cost of their training. They were also required to pay for their tools out of their salaries.
Any apprentice that was felt to be performing below par could be summoned to appear in court at their employer’s request. This could be for turning up late, being ‘idle’ or just having a bad attitude and could even result in a prison sentence. Today, in contrast, apprentices have quality apprenticeship frameworks, their training funded by the government and employer*, their tools are provided, they have access to higher education and employers provide further support through mentoring.
Sue Husband, Director of Apprenticeships at the National Apprenticeship Service said:
The employers of yesterday, like those of today, recognised the value of apprenticeships in equipping people with the skills businesses required. Today more than 100,000 employers are offering quality apprenticeships, building on the example of pioneering forefathers before them.
We urge employers to take stock on what has been achieved in the last century and to consider how apprenticeships could help their business deliver for years to come.
Edward Padgett, owner of Arthur Padgett, a plumbing business in Lancashire that started trading almost 100 years ago and was founded by his grandfather, said:
My grandfather started as an apprentice in plumbing over a century ago. Since then our trade has evolved enormously, but the need for skilled labour is still as relevant today as it was then. That’s why we continue to support apprenticeships.
Reforms to apprenticeships are putting businesses in the driving seat, with employer trailblazers collaborating to design apprenticeship standards and assessment approaches. More than 1,000 companies – from large corporations such as Google, IBM, BAE, PWC, Sky and ITV to SMEs – are involved so far, with the first new apprenticeships due to be delivered in 2014/15.
Krista Cowman, Professor of History at the University of Lincoln said:
Life was tough for apprentices back then. They often started with very menial tasks, working long hours over several years. They could also be summoned to court if they misbehaved. Then, as now though, it is clear how valuable apprenticeships were considered to be in building a skilled workforce.
The research was compiled from 1911 census data from Ancestry, combined with a review of historical research conducted by Professor Krista Cowman at the University of Lincoln.
Since 2010, over 1.9 million young people have started an apprenticeship in England over 170 industries including aerospace, fashion, broadcast media and finance.
PDF , 451KB , 12 pages
If you use assistive technology (such as a screen reader) and need a version of this document in a more accessible format, please email<EMAIL_ADDRESS>. Please tell us what format you need. It will help us if you say what assistive technology you use.


--- layout: post authors: ["Greg Wilson"] title: "Software Engineering Practices in Science" date: 2015-09-16 time: "13:00:00" tags: ["Software Engineering", "Research"] ---
Dustin Heaton and Jeffrey Carver have just published a paper titled Claims About the Use of Software Engineering Practices in Science: A Systematic Literature Review:
Of the 189 papers originally identified, 43 are included in the literature review. These 43 papers contain 33 different claims about 12 software engineering practices... The claims that received the most support were: "The effectiveness of the testing practices currently used by scientic software developers is limited" and "Version control software is necessary for research groups with more than one developer." Additionally, many scientic software developers have unconsciously adopted an agile-like development methodology.
I think this is really useful work, both because it tells us what we do and don't know, and because it shows that questions like these can actually be answered. Recommended.


Class: Aws::CloudWatch::Types::PutCompositeAlarmInput
| Relationships & Source Files | |
| Super Chains via Extension / Inclusion / Inheritance | |
|
Class Chain:
self,
Struct
|
|
|
Instance Chain:
self,
::Aws::Structure ,
Struct
|
|
| Inherits: |
Struct
|
| Defined in: | gems/aws-sdk-cloudwatch/lib/aws-sdk-cloudwatch/types.rb |
Overview
Constant Summary
Instance Attribute Summary
-
#actions_enabled ⇒ Boolean
rw
Indicates whether actions should be executed during any changes to the alarm state of the composite alarm.
-
#actions_suppressor ⇒ String
rw
Actions will be suppressed if the suppressor alarm is in the
ALARM
state. -
#actions_suppressor_extension_period ⇒ Integer
rw
The maximum time in seconds that the composite alarm waits after suppressor alarm goes out of the
ALARM
state. -
#actions_suppressor_wait_period ⇒ Integer
rw
The maximum time in seconds that the composite alarm waits for the suppressor alarm to go into the
ALARM
state. -
#alarm_actions ⇒ Array<String>
rw
The actions to execute when this alarm transitions to the
ALARM
state from any other state. -
#alarm_description ⇒ String
rw
The description for the composite alarm.
-
#alarm_name ⇒ String
rw
The name for the composite alarm.
-
#alarm_rule ⇒ String
rw
An expression that specifies which other alarms are to be evaluated to determine this composite alarm's state.
-
#insufficient_data_actions ⇒ Array<String>
rw
The actions to execute when this alarm transitions to the
INSUFFICIENT_DATA
state from any other state. -
#ok_actions ⇒ Array<String>
rw
The actions to execute when this alarm transitions to an
OK
state from any other state. -
#tags ⇒ Array<Types::Tag>
rw
A list of key-value pairs to associate with the composite alarm.
Instance Method Summary
::Aws::Structure
- Included
| #to_hash | Alias for Structure#to_h. |
Instance Attribute Details
#actions_enabled ⇒ Boolean
(rw)
Indicates whether actions should be executed during any changes to
the alarm state of the composite alarm. The default is TRUE
.
# File 'gems/aws-sdk-cloudwatch/lib/aws-sdk-cloudwatch/types.rb', line 3371
class PutCompositeAlarmInput < Struct.new( :actions_enabled, :alarm_actions, :alarm_description, :alarm_name, :alarm_rule, :insufficient_data_actions, :ok_actions, :, :actions_suppressor, :actions_suppressor_wait_period, :actions_suppressor_extension_period) SENSITIVE = [] include Aws::Structure end
#actions_suppressor ⇒ String
(rw)
Actions will be suppressed if the suppressor alarm is in the ALARM
state. ActionsSuppressor
can be an AlarmName or an Amazon Resource
Name (ARN) from an existing alarm.
# File 'gems/aws-sdk-cloudwatch/lib/aws-sdk-cloudwatch/types.rb', line 3371
class PutCompositeAlarmInput < Struct.new( :actions_enabled, :alarm_actions, :alarm_description, :alarm_name, :alarm_rule, :insufficient_data_actions, :ok_actions, :, :actions_suppressor, :actions_suppressor_wait_period, :actions_suppressor_extension_period) SENSITIVE = [] include Aws::Structure end
#actions_suppressor_extension_period ⇒ Integer
(rw)
The maximum time in seconds that the composite alarm waits after
suppressor alarm goes out of the ALARM
state. After this time, the
composite alarm performs its actions.
ExtensionPeriod
is required only when ActionsSuppressor
is
specified.
# File 'gems/aws-sdk-cloudwatch/lib/aws-sdk-cloudwatch/types.rb', line 3371
class PutCompositeAlarmInput < Struct.new( :actions_enabled, :alarm_actions, :alarm_description, :alarm_name, :alarm_rule, :insufficient_data_actions, :ok_actions, :, :actions_suppressor, :actions_suppressor_wait_period, :actions_suppressor_extension_period) SENSITIVE = [] include Aws::Structure end
#actions_suppressor_wait_period ⇒ Integer
(rw)
The maximum time in seconds that the composite alarm waits for the
suppressor alarm to go into the ALARM
state. After this time, the
composite alarm performs its actions.
WaitPeriod
is required only when ActionsSuppressor
is specified.
# File 'gems/aws-sdk-cloudwatch/lib/aws-sdk-cloudwatch/types.rb', line 3371
class PutCompositeAlarmInput < Struct.new( :actions_enabled, :alarm_actions, :alarm_description, :alarm_name, :alarm_rule, :insufficient_data_actions, :ok_actions, :, :actions_suppressor, :actions_suppressor_wait_period, :actions_suppressor_extension_period) SENSITIVE = [] include Aws::Structure end
#alarm_actions ⇒ Array
<String
> (rw)
The actions to execute when this alarm transitions to the ALARM
state from any other state. Each action is specified as an Amazon
::Aws::CloudWatch::Resource
Name (ARN).
Valid Values: arn:aws:sns:region:account-id:sns-topic-name
|
arn:aws:ssm:region:account-id:opsitem:severity
# File 'gems/aws-sdk-cloudwatch/lib/aws-sdk-cloudwatch/types.rb', line 3371
class PutCompositeAlarmInput < Struct.new( :actions_enabled, :alarm_actions, :alarm_description, :alarm_name, :alarm_rule, :insufficient_data_actions, :ok_actions, :, :actions_suppressor, :actions_suppressor_wait_period, :actions_suppressor_extension_period) SENSITIVE = [] include Aws::Structure end
#alarm_description ⇒ String
(rw)
The description for the composite alarm.
# File 'gems/aws-sdk-cloudwatch/lib/aws-sdk-cloudwatch/types.rb', line 3371
class PutCompositeAlarmInput < Struct.new( :actions_enabled, :alarm_actions, :alarm_description, :alarm_name, :alarm_rule, :insufficient_data_actions, :ok_actions, :, :actions_suppressor, :actions_suppressor_wait_period, :actions_suppressor_extension_period) SENSITIVE = [] include Aws::Structure end
#alarm_name ⇒ String
(rw)
The name for the composite alarm. This name must be unique within the Region.
# File 'gems/aws-sdk-cloudwatch/lib/aws-sdk-cloudwatch/types.rb', line 3371
class PutCompositeAlarmInput < Struct.new( :actions_enabled, :alarm_actions, :alarm_description, :alarm_name, :alarm_rule, :insufficient_data_actions, :ok_actions, :, :actions_suppressor, :actions_suppressor_wait_period, :actions_suppressor_extension_period) SENSITIVE = [] include Aws::Structure end
#alarm_rule ⇒ String
(rw)
An expression that specifies which other alarms are to be evaluated to determine this composite alarm's state. For each alarm that you reference, you designate a function that specifies whether that alarm needs to be in ALARM state, OK state, or INSUFFICIENT_DATA state. You can use operators (AND, OR and NOT) to combine multiple functions in a single expression. You can use parenthesis to logically group the functions in your expression.
You can use either alarm names or ARNs to reference the other alarms that are to be evaluated.
Functions can include the following:
ALARM("alarm-name or alarm-ARN")
is TRUE if the named alarm is in ALARM state.OK("alarm-name or alarm-ARN")
is TRUE if the named alarm is in OK state.INSUFFICIENT_DATA("alarm-name or alarm-ARN")
is TRUE if the named alarm is in INSUFFICIENT_DATA state.TRUE
always evaluates to TRUE.FALSE
always evaluates to FALSE.
TRUE and FALSE are useful for testing a complex AlarmRule
structure, and for testing your alarm actions.
::Aws::CloudWatch::Alarm
names specified in AlarmRule
can be surrounded with
double-quotes ("), but do not have to be.
The following are some examples of AlarmRule
:
ALARM(CPUUtilizationTooHigh) AND ALARM(DiskReadOpsTooHigh)
specifies that the composite alarm goes into ALARM state only if both CPUUtilizationTooHigh and DiskReadOpsTooHigh alarms are in ALARM state.ALARM(CPUUtilizationTooHigh) AND NOT ALARM(DeploymentInProgress)
specifies that the alarm goes to ALARM state if CPUUtilizationTooHigh is in ALARM state and DeploymentInProgress is not in ALARM state. This example reduces alarm noise during a known deployment window.(ALARM(CPUUtilizationTooHigh) OR ALARM(DiskReadOpsTooHigh)) AND OK(NetworkOutTooHigh)
goes into ALARM state if CPUUtilizationTooHigh OR DiskReadOpsTooHigh is in ALARM state, and if NetworkOutTooHigh is in OK state. This provides another example of using a composite alarm to prevent noise. This rule ensures that you are not notified with an alarm action on high CPU or disk usage if a known network problem is also occurring.
The AlarmRule
can specify as many as 100 "children" alarms. The
AlarmRule
expression can have as many as 500 elements. Elements
are child alarms, TRUE or FALSE statements, and parentheses.
# File 'gems/aws-sdk-cloudwatch/lib/aws-sdk-cloudwatch/types.rb', line 3371
class PutCompositeAlarmInput < Struct.new( :actions_enabled, :alarm_actions, :alarm_description, :alarm_name, :alarm_rule, :insufficient_data_actions, :ok_actions, :, :actions_suppressor, :actions_suppressor_wait_period, :actions_suppressor_extension_period) SENSITIVE = [] include Aws::Structure end
#insufficient_data_actions ⇒ Array
<String
> (rw)
The actions to execute when this alarm transitions to the
INSUFFICIENT_DATA
state from any other state. Each action is
specified as an Amazon Resource Name (ARN).
Valid Values: arn:aws:sns:region:account-id:sns-topic-name
# File 'gems/aws-sdk-cloudwatch/lib/aws-sdk-cloudwatch/types.rb', line 3371
class PutCompositeAlarmInput < Struct.new( :actions_enabled, :alarm_actions, :alarm_description, :alarm_name, :alarm_rule, :insufficient_data_actions, :ok_actions, :, :actions_suppressor, :actions_suppressor_wait_period, :actions_suppressor_extension_period) SENSITIVE = [] include Aws::Structure end
#ok_actions ⇒ Array
<String
> (rw)
The actions to execute when this alarm transitions to an OK
state
from any other state. Each action is specified as an Amazon Resource
Name (ARN).
Valid Values: arn:aws:sns:region:account-id:sns-topic-name
# File 'gems/aws-sdk-cloudwatch/lib/aws-sdk-cloudwatch/types.rb', line 3371
class PutCompositeAlarmInput < Struct.new( :actions_enabled, :alarm_actions, :alarm_description, :alarm_name, :alarm_rule, :insufficient_data_actions, :ok_actions, :, :actions_suppressor, :actions_suppressor_wait_period, :actions_suppressor_extension_period) SENSITIVE = [] include Aws::Structure end
#tags ⇒ Array
<Types::Tag> (rw)
A list of key-value pairs to associate with the composite alarm. You can associate as many as 50 tags with an alarm.
Tags can help you organize and categorize your resources. You can also use them to scope user permissions, by granting a user permission to access or change only resources with certain tag values.
# File 'gems/aws-sdk-cloudwatch/lib/aws-sdk-cloudwatch/types.rb', line 3371
class PutCompositeAlarmInput < Struct.new( :actions_enabled, :alarm_actions, :alarm_description, :alarm_name, :alarm_rule, :insufficient_data_actions, :ok_actions, :, :actions_suppressor, :actions_suppressor_wait_period, :actions_suppressor_extension_period) SENSITIVE = [] include Aws::Structure end


Daily Tracker
HOW ARE YOUFEELING TODAY?
Surgical Site Picture
Fluid Balance
Cups of Water
Number of Pouch Changes
Number of Times Urinated
Abdominal Health
No Passing of Stool (24Hrs)
No
Yes
No Passing of Gas (24Hrs)
No
Yes
Abdominal Pain
No
Yes
Abdominal Swelling
No
Yes
Wound Care
Wound Open
No
Yes
Wound Pain/Swelling/Redness
No
Yes
Home
My Health
Settings


PourOver = {
PourOver = {
Utility functions. Skip down to "Collections" for the real meat of PourOver.
union_sorted: function(a,b){
Make more efficient by just copying at Infinity
var lowa = 0, lowb = 0, higha = a.length, highb = b.length, result=[], la, lb;
while (higha > lowa || highb > lowb){
la = a[lowa];
lb = b[lowb];
if(_.isUndefined(la)) la = Infinity;
if(_.isUndefined(lb)) lb = Infinity;
if(lowa == higha){
return result.concat(b.slice(lowb,highb));
}
if(lowb == highb){
return result.concat(a.slice(lowa,higha));
}
if(la == lb){
result.push(la);
lowa++;lowb++;
} else if (la < lb){
result.push(la);
lowa++;
} else {
result.push(lb);
lowb++;
}
}
return result;
},
intersect_sorted: function(a,b){
var lowa = 0, lowb = 0, higha = a.length, highb = b.length, result=[], la, lb;
while (higha > lowa && highb > lowb){
la = a[lowa];
lb = b[lowb];
if(la == lb){
result.push(la);
lowa++;lowb++;
} else if (la < lb){
lowa++;
} else {
lowb++;
}
}
return result;
},
subtract_sorted: function(a,b){
var lowa = 0, lowb = 0, higha = a.length, highb = b.length, result=[], la, lb;
while (higha > lowa || highb > lowb){
la = a[lowa];
lb = b[lowb];
if(higha == lowa){
return result;
}
if(highb == lowb){
return result.concat(a.slice(lowa,higha));
}
if(la == lb){
lowa++;lowb++;
} else if (la < lb){
result.push(la);
lowa++;
} else {
lowb++;
}
}
return result;
},
insert_sorted: function(set,element){
var length = set.length,
i = 0,
last_elem = set[length - 1];
if(element > last_elem){
set.push(element);
return set;
}
while(i < length){
if(element < set[i]){
return set.slice(0,i).concat([element]).concat(set.slice(i,length));
} else {
i++;
}
}
set.push(element);
return set;
},
Sort the set according to some function and then store an array of the translations of the indicies. So if the first item went to index 2 after being sorted, put 2 in the first spot of the permutation array.
build_permutation_array: function(set,sort){
var sorted_set = _(set).clone(),perm=[];
if(typeof(sort) === "function"){
sorted_set.sort(sort);
} else {
sorted_set.sort(function(a,b){return sort.fn.call(sort,a,b)});
}
_(sorted_set).each(function(m,i){perm[m.cid] = i;});
return perm;
},
Use a permutation array to resort a subset of a collection.
permute_from_array: function(collection,perm){
var output = [];
if(typeof(collection[0]) === "number"){
_(collection).each(function(i){ output[perm[i]] = i });
} else {
_(collection).each(function(i){ output[perm[i.cid]] = i });
}
return _(output).without(undefined);
},
Remove an element from a sorted set.
remove_sorted: function(set,element){
var length = set.length,
i = 0;
while(i < length){
if(element == set[i]){
return set.slice(0,i).concat(set.slice(i+1,length));
} else {
i++;
}
}
return set;
},
Caching is really the raison d'etre of Pourover. Every filter has two cache methods: one for rebuilding the whole filter from scratch and one for adding new items. As Pourover grows it will gain more pre-defined cache methods that correlate with common UI and data patterns.
cacheMethods: {
Just goes through each possible value for the filter and tests every item in the collection against it. As expensive as possible, but simple.
defaultCache: function(items){
var that = this;
_(that.possibilities).each(function(p){
var matching_items = _(items).filter(function(i){return that.fn(p,i)}),
matching_cids = _(matching_items).map(function(i){return i.cid});
p.matching_cids = matching_cids;
});
},
defaultAddCache: function(items){
var that = this;
_(that.possibilities).each(function(p){
var matching_items = _(items).filter(function(i){return that.fn(p,i)}),
matching_cids = _(matching_items).map(function(i){return i.cid});
p.matching_cids = PourOver.union_sorted(p.matching_cids,matching_cids)
});
},
For filters that evaluate by strict equality (this property === this value). The name of the filter must match the name of the property for exact cache to work.
exactCache: function(items){
var that = this;
_(items).each(function(i){
var p = that.possibilities[i[that.name]];
if (p) {
p.matching_cids = PourOver.insert_sorted(p.matching_cids,i.cid)
}
});
},
exactAddCache: function(items){
var that = this;
_(items).each(function(i){
var p = that.possibilities[i[that.name]];
if (p) {
p.matching_cids = PourOver.insert_sorted(p.matching_cids,i.cid)
}
});
}
}
}
The main kind of object in Pourover. A collection is basically a wrapper around an array of objects. It adds collection ids to its members and has support for various ways of retrieving all or a part of its members.
PourOver.Collection = function(items,opts){
if(typeof(items) == "undefined"){items = [];}
this.items = [];
this.filters = {};
this.sorts = {}
this.addItems(items)
this.on("change",function(){
_(this.filters).each(function(f){ if(f.current_query){f.current_query.refresh();} });
})
this.initialize.apply(this, arguments);
}
_.extend(PourOver.Collection.prototype,Backbone.Events,{
initialize: function(){},
Force the filters and sorts of a collection to refresh.  Like everything in Pourover, the cids must be sorted.
This is not usually an issue as you generally will not be calling collection.get
with an array you
manually create. You will probably be using the output of some function that keeps it sorted for you.
get: function(cids){
return this.getBy("cid",cids)
},
Similar to get, except -- rather than getting items by cid -- you are getting them by [attr_name]. Here vals is an array of [attr_names]s.
getBy: function(attr_name,vals){
if(! _.isArray(vals)){ var vals = [vals] }
var low = 0, high = this.items.length,lc = 0, hc = vals.length, output = [],items = this.items,i;
while (low < high && lc < hc){
if (vals[lc] == (i=items[low])[attr_name]){
output.push(i);
low++;
lc++;
} else if (vals[lc] < i[attr_name]){
lc++;
} else{
low++;
}
}
return output;
},
Add items to the collection, triggering the appropriate events to keep all dependent sort and filter sets up-to-date.
addItems: function(i){
if(! _.isArray(i)){ var i = [i] }
var last_id = this.items.length > 0 ? _(this.items).last().cid + 1 : 0,new_items;
new_items = _(i).map(function(c){var n = PourOver.Item(c); n.cid = last_id++; return n;});
this.items = this.items.concat(new_items);
this.regenerateFilterSets(new_items);
this.trigger("change");
},
Remove items from the collection, triggering the appropriate events to keep all dependent sort and filter sets up-to-date. This functionality is only included begrudgingly. Pourover is best for collections that rarely remove members. TODO: Optimize
removeItems: function(i,isSorted){
if(typeof(isSorted) === "undefined"){var isSorted = false}
if(! _.isArray(i)){ var i = [i] }
if(isSorted){
i = i.sort(function(a,b){return a.cid - b.cid });
var new_items = [],old_items = this.items,new_length = i.length,old_length = this.items.length,newi = 0, oldi = 0;
while(oldi < old_length){
if(! newi < new_length){
new_items = new_items.concat(old_items.slice(oldi))
break;
} else if(old_items[oldi].cid === i[newi].cid){
newi++;
oldi++;
} else {
new_items.push(old_items[oldi]);
oldi++;
}
}
} else {
var new_items = [], old_items = this.items,old_length = this.items.length, oldi = 0,delete_cids = _(i).pluck("cid");
while(oldi < old_length && delete_cids.length > 0){
if(_(delete_cids).include(old_items[oldi].cid)){
} else {
new_items.push(old_items[oldi]);
}
oldi++;
}
}
this.items = new_items;
this.regenerateFilterSets();
this.trigger("change");
},
All filters are associated to collections rather than views. This allows for multiple views to share the same filter. This is especially useful for modal situations in which you can set filters on a grid view that are reflected in the one up view as well.
addFilters: function(f){
var that = this,new_filters;
if(! _.isArray(f)){ var f = [f] }
new_filters = _(f).reduce(function(m,i){ m[i.name] = _.clone(i); m[i.name].collection = that; return m; },{});
this.filters = _(this.filters).extend(new_filters);
Bubble all query change events up from the individual filters to the collection. This allows a developer to specify events that should be triggered whenever any filter's query is changed.
_(new_filters).each(function(f){
f.on("queryChange",function(){
that.trigger("queryChange");
})
All filters precache the result of their filtering. This is the source of pourover's speed optimizations.
f.cacheResults(that.items);
If a user passes in an associated_attrs
property on a filter, that filter will re-cache its result whenever
any object in the collection has an attribute changed. Setting associated_attrs
is essential for admins or
other uses in which filterable values can change.
if(f.associated_attrs){
_(f.associated_attrs).each(function(a){
that.on("change:"+a,function(objs){
f.removeFromCache(objs);
f.addCacheResults(objs);
if(f.current_query){f.current_query.refresh();}
});
});
}
});
},
A shortcut to re-calculate the results of every filter. This is expensive if you do not pass in new_items
, in which cases
only the new_items will be cached and the filters updated.
regenerateFilterSets: function(new_items){
var that = this;
If no new items are passed in, regenerate filters for all items in the collection
if(typeof(new_items) == "undefined"){
_(this.filters).each(function(f){
f.cacheResults(that.items)
});
} else {
_(this.filters).each(function(f){
f.addCacheResults(new_items)
});
}
},
A shortcut for returning a match object containing all the items in a collection. More on matches below.
getAllItems: function(){
var cids = _(this.items).map(function(i){return i.cid});
return new PourOver.MatchSet(cids,this,["all"]);
},
Get the currently cached results for the last stateful query on a filter (the last time a setQuery
was called on that filter.)
If empty_default
is set to true, the function will return no items if the filter does not have a current query set. Otherwise,
the function will return all items in the collection. The former empty_default
setting is useful when OR-ing filters together, when
you want an unset filter to represent an unselected dimension. The latter is useful when AND-ing filters together, when you
want an unset filter to comprise all objects in the collection.
getCurrentFilteredItems: function(filter_name,empty_default){
if(typeof(empty_default) === "undefined"){empty_default = false;}
if(this.filters[filter_name].current_query && this.filters[filter_name].current_query.stack.length > 0){
return this.filters[filter_name].current_query;
} else {
if(empty_default){
return new PourOver.MatchSet([],this,[]);
} else {
return this.getAllItems();
}
}
},
The non-stateful way to query a filter. Simply returns the result of the query but does not store the query on the filter.
getFilteredItems: function(filter_name,query){
var filter = this.filters[filter_name],possibility;
if (_.isUndefined(filter) ) throw "The filter " + filter_name + " does not exist."
return filter.getFn(query);
},
Sorts, like filters, are generally stored on collections for the same reason that filters are stored on the collection rather than the view. However, whereas filters keep track of their own state and this is shared between views, the state of which sort is enabled is stored on the view.
addSort: function(sort){
var that = this;
this.sorts[sort.name] = sort;
sort.collection = this;
sort.rebuild_sort();
this.on("change",function(){ sort.rebuild_sort(); });
Like filters, if you set associated_attrs
on a sort, they will rebuild themselves whenever any item in the collection undergoes a change
on that attribute.
TODO: Consider cloning on add. Also, bring in line with addFilter (events or not!?)
if(sort.associated_attrs){
_(sort.associated_attrs).each(function(a){
that.on("change:"+a,function(objs){
sort.rebuild_sort();
});
});
}
},
Add multiple sorts.
addSorts: function(sorts){
if(typeof(opts) === "undefined"){ opts = {};}
if(! _(sorts).isArray()){sorts = [sorts]}
var that = this;
_(sorts).each(function(s){
that.addSort(s);
});
},
The non-stateful way to retrieve all the items in the collection, sorted.
getSortedItems: function(sort_name){
var s = this.sorts[sort_name],that = this,output;
return s.sort(this.items);
},
A silly shortcut, pass in a cid and an attribute, retrieve its value. Useful for template helpers.
getItemValue: function(cid,attribute){
var item = _(this.items).find(function(i){return i.cid === Number(cid);});
return item[attribute];
},
Update the value of one attribute of one item in the collection.
updateItem: function(cid,attribute,value){
var item = _(this.items).find(function(i){return i.cid === Number(cid);});
item[attribute] = value;
this.trigger("incremental_change",[attribute]);
this.trigger("update","updateItem");
this.trigger("change:"+attribute,[item]);
return item.guid;
},
Change the value of one attribute of many items to the same value.
batchUpdateItems: function(cids,attribute,value){
var items = this.get(cids,true);
_(items).each(function(i){
i[attribute] = value;
});
this.trigger("update","batchUpdate");
this.trigger("change:"+attribute,items);
this.trigger("incremental_change",[attribute]);
return _(items).pluck("guid");
},
Change the value of several attributes of a single item in the collection.
updateAttributes: function(cid,updates){
var item = _(this.items).find(function(i){return i.cid === Number(cid);});
var that = this;
_(updates).each(function(v,k){
item[k] = v;
that.trigger("change:"+k,[item]);
});
this.trigger("update","updateAttribute");
this.trigger("incremental_change",_(updates).keys());
return item.guid;
},
Change the value of several attributes of several items in the collection. Here 'updates' is a hash of attributes -> new values.
batchUpdateAttributes: function(cids,updates){
var items = this.get(cids,true)
var that = this;
_(items).each(function(item){
_(updates).each(function(v,k){
item[k] = v;
});
})
_(updates).each(function(v,k){
that.trigger("change:"+k,items);
});
this.trigger("update","batchUpdateAttribute");
this.trigger("incremental_change",_(updates).keys());
return _(items).pluck("guid");
}
});
PourOver.Item = function(i){
return i;
},
A filter is basically a rule for mapping items of a collection into groups based on attribute values. It caches the results and can be queried either statefully or non-statefully, depending on developer preference.
PourOver.Filter = function(name,values,opts){
if(typeof(opts) === "undefined"){opts = {}}
this.name = name;
this.possibilities = this.create_possibilities(values);
this.values = _(values).map(function(v){return v.value});
_.extend(this,opts);
this.initialize.apply(this, arguments);
}
_.extend(PourOver.Filter.prototype,Backbone.Events,{
Initialize is a no-op by default.
initialize: function(){},
Given an array of possible values, initializes the object that will store the cached results of querying for that possibility.
create_possibilities: function(vs){
var o = {};
_(vs).each(function(v){
var name = v.name || String(v.value);
o[name] = v;
o[name].matching_cids = [];
})
return o;
},
cacheResults and addCacheResults are generic methods that must be overridden before instantiating a filter. The preset filters included below provide good examples of how these functions should be written. cacheResults should cache all the items in the collection, whereas addCacheResults incrementally adds new items to already cached, filtered results.
cacheResults: function(items){
throw "No cache function has been defined for this filter '" + this.name + "'."
},
addCacheResults: function(items){
throw "No add cache function has been defined for this filter '" + this.name + "'."
},
Generally only used when removing items from a collection or when an item changes value. This will remove the item from the cache so that it can either be recached with its new value or thrown away.
removeFromCache: function(items){
var cids = _(items).map(function(i){return i.cid}).sort(function(a,b){return a-b;});
_(this.possibilities).each(function(p){
p.matching_cids = PourOver.subtract_sorted(p.matching_cids,cids);
});
},
The stateful way to query a filter. Delegates the retrieval of a MatchSet to the filter's getFn and caches the results on the filter.
query: function(q,silent){
if(typeof(silent) === "undefined"){var silent = false;}
var match_set = this.getFn(q);
this.setQuery(match_set,silent);
},
Assigns a MatchSet to a filter (caches the result) and triggers the appropriate events.
setQuery: function(q,silent){
if(typeof(silent) === "undefined"){var silent = false;}
this.current_query = q;
if(!silent){
this.trigger("queryChange");
}
},
Removes a cached result from a filter.
clearQuery: function(silent){
if(typeof(silent) === "undefined"){var silent = false;}
this.current_query = false;
if(!silent){
this.trigger("queryChange");
}
},
Unions a cached result with another result (both being MatchSets) and produces a new MatchSet.
unionQuery: function(q,silent){
if(typeof(silent) === "undefined"){var silent = false;}
if(typeof(q) === "string" || typeof(q) === "number"){
var q = this.getFn(q);
}
if(this.current_query){
this.current_query = this.current_query.or(q);
} else {
this.current_query = q;
}
if(!silent){
this.trigger("queryChange");
}
},
Intersects a cached result with another result (both being MatchSets) and produces a new MatchSet.
intersectQuery: function(q,silent){
if(typeof(silent) === "undefined"){var silent = false;}
if(typeof(q) === "string" || typeof(q) === "number"){
var q = this.getFn(q);
}
if(this.current_query){
this.current_query = this.current_query.and(q);
} else {
this.current_query = q;
}
if(!silent){
this.trigger("queryChange");
}
},
Subtracts a cached result with another result (both being MatchSets) and produces a new MatchSet.
subtractQuery: function(q,silent){
if(typeof(silent) === "undefined"){var silent = false;}
if(typeof(q) === "string" || typeof(q) === "number" || _.isArray(q)){
var q = this.getFn(q);
}
if(this.current_query){
this.current_query = this.current_query.not(q);
} else {
this.current_query = q;
}
if(!silent){
this.trigger("queryChange");
}
},
This is the inverse of the three functions above. Removes a query from a compound, cached MatchSet on a filter. This is useful when you have a UI in which subsequent selections union together. It is faster on a toggle to remove the deselected possibility rather than re-union the remaining selected ones. TODO: Test
removeSingleQuery: function(q,silent){
if(! this.current_query){return false;}
if(typeof(silent) === "undefined"){var silent = false;}
if(typeof(q) === "string" || typeof(q) === "number" || _.isArray(q)){
var q = this.getFn(q);
}
var s = [],
stack = this.current_query.stack,new_stack;
new_stack = _(stack).reduce(function(m,i){
if(i[1] === q.stack[0][1]){
return m;
} else if(i[0] === "or" && i[1][0][1] === q.stack[0][1]){
return m;
} else if(i[0] === "and" && i[1][0][1] === q.stack[0][1]){
return m;
} else if(i[0] === "not" && i[1][0][1] === q.stack[0][1]){
return m;
} else {m.push(i); return m;}
},s);
this.current_query.stack = new_stack;
this.current_query.refresh();
if(!silent){
this.trigger("queryChange");
}
},
Convenice method for getting the collection attached to a filter. Just an aesthetic thing. I like the explicit "getCollection" calls in the rest of the code.
getCollection: function(){
return this.collection;
}
});
Sorts cache different orderings of collection items and subsets thereof. Sorts generally belong to collections, but they can belong to views as well for optimization concerns.
PourOver.Sort = function(name,opts){
this.name = name;
_.extend(this,opts)
this.initialize.apply(this, arguments);
}
_.extend(PourOver.Sort.prototype,Backbone.Events,{
initialize: function(){},
By default, sorts are not view sorts. A view sort is attached to a specific view and only updates when that view undergoes a queryChange.
view: false,
Use a sort to order an array of cids
sort: function(set){return PourOver.permute_from_array(set,this.permutation_array)},
Recache the results of sorting the collection.
rebuild_sort: function(){
if(this.view){
var items = this.view.match_set.all();
} else {
var items = this.collection.items;
}
this.permutation_array = PourOver.build_permutation_array(items,this);
this.trigger("resort");
}
});
Views store a state of collection and are generally what should be rendered. There can be many views per collection. Views can be paged. Moreover, a view has a selection function which tells the view how to compose its various filters to produce the current set.
PourOver.View = function(name,collection,opts){
var that = this;
this.name = name;
if(typeof(opts) === "undefined"){ opts = {};}
this.collection = collection;
this.match_set = new PourOver.MatchSet(_(this.collection.items).map(function(i){return i.cid}),this.collection,["all"]);
if(opts.template){this.template = opts.template};
Whenever the collection gains or loses members, recache the MatchSet saved on the view.
this.collection.on("change",function(){
that.match_set.refresh();
that.setNaturalSelection();
});
Whenever an item in the collection is changed, recache the MatchSet saved on the view.
this.collection.on("incremental_change",function(attrs){
that.match_set.refresh();
that.setNaturalSelection(attrs);
});
Bubble all collection update events through.
this.collection.on("update",function(f){
that.trigger("update",f);
});
Whenever any filter is queried statefully, reset the view's MatchSet; We don't have to refresh the match_set here. That is only necessary when it's possible that a filter has stale information as a result of a change in the underlying data.
this.collection.on("queryChange",function(){
that.setNaturalSelection();
that.trigger("update","query");
});
Bubble up sortChange events as updates
this.on("sortChange",function(){
this.trigger("update","sort");
})
Bubble up sortChange events as updates
this.on("pageChange",function(){
this.trigger("update","page");
})
_.extend(this,opts);
this.initialize.apply(this, arguments);
}
_.extend(PourOver.View.prototype,Backbone.Events,{
initialize: function(){},
current_page: 0,
view_sorts: [],
By default, return all items in the view.
page_size: Infinity,
current_sort: false,
Changes a view from being sorted to no longer being sorted.
removeSort: function(){
if(this.current_sort.off){this.current_sort.off("resort");}
this.current_sort = false;
this.trigger("sortChange");
},
Sets a sort on a view and fires all appropriate events.
setSort: function(sort_name,view_sort){
if(typeof(view_sort) === "undefined"){view_sort = false}
var that = this;
if(this.current_sort.off){this.current_sort.off("resort");}
if(sort_name && view_sort){
this.current_sort = this.view_sorts[sort_name];
this.current_sort.on("resort",function(){that.trigger("sortChange")});
} else if(sort_name){
this.current_sort = this.collection.sorts[sort_name];
this.current_sort.on("resort",function(){that.trigger("sortChange")});
} else {
this.current_sort = false;
}
this.trigger("sortChange");
},
Return the name of the current sort of the view.
getSort: function(){
if (!this.current_sort){
return false;
} else {
return this.current_sort.name;
}
},
Add a sort to the view. The difference between this and a collection sort is that this sort will only change if the view receives a selectionChange.
addViewSorts: function(sorts){
if(typeof(opts) === "undefined"){ opts = {};}
if(! _(sorts).isArray()){sorts = [sorts]}
var that = this;
_(sorts).each(function(sort){
that.view_sorts[sort.name] = sort;
sort.collection = that.collection;
sort.view = that;
sort.rebuild_sort();
that.on("selectionChange",function(attrs){
if(sort.associated_attrs == undefined){
sort.rebuild_sort();
}
if(sort.associated_attrs && _.intersection(sort.associated_attrs,attrs).length > 0){
sort.rebuild_sort();
}
});
});
},
IMPORTANT: This determines how a view composes the filters on a collection to generate results. Here, by default, every filter on the collection is intersected. This is often the desired behavior. However, this must be overridden if you want your view to do fancier things such as union some filters, difference others, and intersect the rest.
selectionFn: function(){
var collection = this.collection;
var output = _(collection.filters).reduce(function(m,i){
var q = i.current_query;
if(m && (!q || _.isEmpty(q.stack))){ return m;}
if(!m && !q){return collection.getAllItems();}
if(m){
return m.and(q);
} else {
return q;
}
},false);
return output;
},
Caches a MatchSet on the view as the current match_set;
setSelection: function(match_set,attrs){
this.match_set = match_set;
this.trigger("selectionChange",attrs);
},
Delegates to the views selectionFn to generate an array of valid cids given the current filters.
setNaturalSelection: function(attrs){
var selection;
selection = this.selectionFn();
this.setSelection(selection,attrs);
},
Removes a MatchSet from a view and replaces it with the universe of possible items.
clearSelection: function(){this.match_set = this.collection.getAllItems();},
IMPORTANT: This is the function you will call most often on views. This returns the cached, filtered items and then sorts them and pages them as appropriate.
getCurrentItems: function(page){
if(! this.match_set){return []}
if(typeof(page) === "undefined"){
var page = this.current_page;
}
if(this.page_size == Infinity){
if(this.current_sort){
var items = this.match_set.all_sorted(this.current_sort);
} else {
var items = this.match_set.all();
}
} else {
TODO: Slice cids before reassociating
if(this.current_sort){
var items = this.match_set.all_sorted_cids(this.current_sort);
items = items.slice(this.page_size * page,this.page_size * (page + 1));
var ordered_cids = _(items).clone().sort(function(a,b){return a-b});
var unsorted_items = this.collection.get(ordered_cids);
items = _(items).map(function(i){return _(unsorted_items).find(function(o){return o.cid === i}) });
} else {
var items = this.match_set.cids;
items = items.slice(this.page_size * page,this.page_size * (page + 1));
items = this.collection.get(items);
}
}
return items;
},
Change the page of the view by [dir] pages. Negative values to page back.
page: function(dir){
var new_dir = dir + this.current_page;
if(new_dir < 0) new_dir = 0;
if(new_dir > Math.ceil(this.match_set.length()/this.page_size - 1)) new_dir = Math.ceil(this.match_set.length()/this.page_size - 1);
this.current_page = new_dir;
this.trigger("pageChange");
},
Page to a specific cid. This IS NOT to page to a specific page.
pageTo: function(cid){
if(this.current_sort){
var index = _(this.match_set.all_sorted_cids(this.current_sort)).indexOf(cid),
len = this.match_set.cids.length,
page = Math.ceil(index/this.page_size);
} else {
var index = _(this.match_set.cids).indexOf(cid),
len = this.match_set.cids.length,
page = Math.ceil(index/this.page_size);
}
this.current_page = page;
this.trigger("pageChange");
},
Set the page size.
setPageSize: function(size){
this.page_size = size;
this.trigger("pageChange");
},
render: function(){}
})
These are what are returned from queries on filters. They can be chained together with ands, or, & nots. They also keep a "stack" to remember how they were created (after chaining) so that they can refresh themselves.
PourOver.MatchSet = function(cids,collection,stack){
this.cids = cids;
this.collection = collection
this.stack = stack;
this.initialize.apply(this, arguments);
}
_.extend(PourOver.MatchSet.prototype,Backbone.Events,{
initialize: function(){},
When the underlying data has changed re-evaluate which items are included in this possibly compound result.
refresh: function(s,match_set){
if(typeof(s) === "undefined"){var s = this.stack || []}
if(s.length < 1 && match_set){
this.cids = match_set.cids;
return this;
} else if (s.length < 1){
this.cids = false;
return this;
}
var step = s[0],
operation = step[0];
if(typeof(operation) === "object"){
var match_set = operation.getFn(step[1]);
return this.refresh(_(s).rest(),match_set)
} else if(operation === "all" || step === "all") {
var cids = _(this.collection.items).map(function(i){return i.cid});
var match_set = new PourOver.MatchSet(cids,this,["all"]);
return this.refresh(_(s).rest(),match_set)
} else if(operation === "and") {
if(match_set){
var m = match_set.and(this.refresh(step[1]));
} else {
var m = this.refresh(step[1]);
}
return this.refresh(_(s).rest(),m)
} else if(operation === "or") {
if(match_set){
var m = match_set.or(this.refresh(step[1]));
} else {
var m = this.refresh(step[1]);
}
return this.refresh(_(s).rest(),m)
} else if(operation === "not") {
if(match_set){
var m = match_set.not(this.refresh(step[1]));
} else {
var m = this.refresh(step[1]);
}
return this.refresh(_(s).rest(),m)
}
},
Intersect this MatchSet with another MatchSet.
and: function(other_matches){
if(this.stack.length < 1 && other_matches){
return other_matches;
} else if (!other_matches){
return this;
} else {
var set = PourOver.intersect_sorted(this.cids,other_matches.cids);
return new PourOver.MatchSet(set,this.collection,this.stack.concat([["and",other_matches.stack]]))
}
},
Union this MatchSet with another MatchSet.
or: function(other_matches){
if(this.stack.length < 1 && other_matches){
return other_matches;
} else if (!other_matches){
return this;
} else {
var set = PourOver.union_sorted(this.cids,other_matches.cids);
return new PourOver.MatchSet(set,this.collection,this.stack.concat([["or",other_matches.stack]]));
}
},
Difference this MatchSet with another MatchSet.
not: function(other_matches){
if(this.stack.length < 1 || ! other_matches){
return this;
} else {
var set = PourOver.subtract_sorted(this.cids,other_matches.cids);
return new PourOver.MatchSet(set,this.collection,this.stack.concat([["not",other_matches.stack]]));
}
},
Return all the items corresponding to the cids cached on the MatchSet.
all: function(){ return this.collection.get(this.cids);},
Return a slice of the items corresponding to the cids cached on the MatchSet.
slice: function(s,e){ return this.collection.get(this.cids.slice(s,e)) },
Return all the items corresponding to the cids cached on the MatchSet AND sorted by sort s.
all_sorted: function(s){ var c = this.all(); return s.sort(c); },
Sort the cached cids.
all_sorted_cids: function(s){ var c = this.cids; return s.sort(c); },
Return how many items comprise this MatchSet.
length: function(){return this.cids.length}
});
PourOver.UI is a simple add-on for creating objects to be rendered as UI elements controlling the state of filters and views.
PourOver.UI = {}
PourOver.UI.Element = function(opts){
if(typeof(opts) === "undefined"){var opts = {}}
this.initialize.apply(this,arguments)
}
_.extend(PourOver.UI.Element.prototype,Backbone.Events,{
initialize: function(){},
getMatchSet: function(){
throw "No get match set function specified"
},
getFilterState: function(){
throw "No get filter state specified";
},
template: function(){
throw "No template specified"
},
render: function(){
var filter_state = this.getFilterState();
output = this.template({state:filter_state});
return output
},
Pass in a MatchSet that only has a single query of a chain of OR'ed queried and receive an array of possibility names that have been selected.
getSimpleSelectState: function(match_set,s,output){
if(typeof(match_set) === "undefined" || !match_set || !match_set.stack){return false}
if(typeof(s) === "undefined"){var s = match_set.stack}
if(typeof(output) === "undefined"){var output = []}
if(s.length < 1){
return output;
} else if (typeof(s[0][0]) === "object"){
output.push(s[0][1]);
return this.getSimpleSelectState(match_set,_(s).rest(),output);
} else if (s[0][0] === "or"){
output = output.concat(this.getSimpleSelectState(match_set,s[0][1]));
return this.getSimpleSelectState(match_set,_(s).rest(),output);
} else {
throw "This does not appear to be a valid, simple selectElement stack."
}
},
Pass in a MatchSet that is the result of a single, non-compounded range and receive the value of that range.
getSimpleRangeState: function(match_set){
if(typeof(match_set) === "undefined" || !match_set || !match_set.stack){return false}
stack = match_set.stack;
if(stack.length !== 1 || stack[0][1].length !== 2){throw "The filter specified does not appear to have a simple range stack."}
return stack[0][1];
}
TODO: Added more UI gets.
});
From Backbone
Helper function to correctly set up the prototype chain, for subclasses.
Similar to goog.inherits
, but uses a hash of prototype properties and
class properties to be extended.
PourOver.extend = function(protoProps, staticProps) {
var parent = this;
var child;
The constructor function for the new subclass is either defined by you
(the "constructor" property in your extend
definition), or defaulted
by us to simply call the parent's constructor.
if (protoProps && _.has(protoProps, 'constructor')) {
child = protoProps.constructor;
} else {
child = function() {
return parent.apply(this, arguments);
};
}
Add static properties to the constructor function, if supplied.
_.extend(child, parent, staticProps);
Set the prototype chain to inherit from parent
, without calling
parent
's constructor function.
var Surrogate = function() {
this.constructor = child;
};
Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate;
Add prototype properties (instance properties) to the subclass, if supplied.
if (protoProps) _.extend(child.prototype, protoProps);
Set a convenience property in case the parent's prototype is needed later.
child.__super__ = parent.prototype;
return child;
};
PourOver.Collection.extend = PourOver.View.extend = PourOver.Filter.extend = PourOver.Sort.extend = PourOver.MatchSet.extend = PourOver.UI.Element.extend = PourOver.extend
A PourOver buffered collection is one that stores some or all of its data as a promise. This is useful in conjunction with a large data set in which you don't want to load all the data at page open.
PourOver.BufferedCollection = PourOver.Collection.extend({
buffered_items: {},
stripFutures: function(item){
return _(item).reduce(function(m,v,k){if(typeof(v) != "undefined"){m[k] = v} return m},{});
},
Overrides the base get function with one that buffers in whole values from the server.
get: function(cids,raw){
if(typeof(raw) === "undefined"){var raw = false}
var items = PourOver.Collection.prototype.get.call(this,cids),
that = this;
if(raw){return items;}
return _(items).map(function(i){
var guid = i.guid, new_item;
if(that.buffered_items.hasOwnProperty(guid)){
return _(that.buffered_items[guid]).extend(that.stripFutures(i));
} else {
return i;
}
});
},
Retrieve a specific attr of a specific item from the buffer.
getBufferedValue: function(guid,attr){
if(this.buffered_items.hasOwnProperty(guid)){
return this.buffered_items[guid][attr] || false;
} else {
return false;
}
},
Delete all buffered values for items in the collection.
clearBufferedItems: function (){
var buffered_items = this.buffered_items;
for (var p in buffered_items){
if (buffered_items.hasOwnProperty(p)){
delete buffered_items[p];
}
}
},
When instantiating a buffered collection you must provide this method. This is how a buffered collection knows what URL to fetch new data from.
getBufferUrl: function(guids){
throw "You must override getBufferUrl;"
},
preprocessItem: function(item){
return [item["guid"],item]
},
Pull down new data for an array of guids from the server at the URL returned by getBufferUrl. When the request returns, push the new values into the buffer. The deferred object is returned from this method so you can chain additional callbacks onto the resolution such as a render action.
bufferGuids: function(guids){
var that = this,
guids = _(guids).select(function(g){ return g && ! that.buffered_items.hasOwnProperty(g);}),
buffurl = this.getBufferUrl(guids),
url = buffurl[0],
jsonpCallback = buffurl[1];
if(guids.length > 0){
return $.ajax({
url: url,
dataType:'jsonp',
cache: true
}).always(function(d){
if(_.isArray(d)){
items = _(d).map(_.bind(that.preprocessItem,that));
_(items).each(function(i){
that.buffered_items[i[0]] = i[1];
});
}
});
} else {
return $.Deferred().resolve(false);
}
}
});
A buffered view is the pair to a buffered collection. It calls the appropriate buffering methods of the buffered collection so that you automatically get the benefits of buffering as you are paging through the view. If you use a buffered view with a buffered collection you shouldn't need to call the buffering methods of the collection explicitly.
PourOver.BufferedView = PourOver.View.extend({
buffer_pages: 1,
bufferAroundCurrentPage: function(){
var current_page = this.current_page,
low_bound = current_page - this.buffer_pages > 0 ? current_page - this.buffer_pages : 0,
high_bound = current_page + this.buffer_pages,
range = _.range(low_bound,high_bound + 1),
that = this;
range = _(range).map(function(page){
return _(that.getCurrentItems(page)).pluck("guid");
});
var guids = _.flatten(range);
buffer_deferred = this.collection.bufferGuids(guids);
buffer_deferred.done(function(d){
if(d){
that.render();
}
})
},
page: function(dir){
PourOver.View.prototype.page.call(this,dir);
this.bufferAroundCurrentPage();
},
pageTo: function(cid){
PourOver.View.prototype.pageTo.call(this,cid);
this.bufferAroundCurrentPage();
}
})
A strange filter that selects items based on an explicit list of cids. This is useful when you want to use PourOver in association with, say, an editorially composed list of items or any mechanic in which you can "select" items to be included in a filter independent of any attribute.
PourOver.manualFilter = PourOver.Filter.extend({
cacheResults: function(){return false},
addCacheResults: function(){return false},
getFn: function(query){
if(_(query).isArray()){
query = query.sort(function(a,b){return a - b})
return new PourOver.MatchSet(query,this.getCollection(),[[this,query]]);
} else if (typeof(query) === "number") {
return new PourOver.MatchSet([query],this.getCollection(),[[this,query]]);
} else {
throw "Manual filters only support querying by one or more cids"
}
},
addItems: function(cids){
if(! _(cids).isArray()){cids = [cids]}
cids = cids.sort(function(a,b){return a - b});
if(this.current_query){
var current_query = this.current_query.cids,
new_query = PourOver.union_sorted(current_query,cids);
} else {
var new_query = cids;
}
this.query(new_query);
},
removeItems: function(cids){
if(! _(cids).isArray()){cids = [cids]}
cids = cids.sort(function(a,b){return a - b});
var current_query = this.current_query.cids,
new_query = PourOver.subtract_sorted(current_query,cids);
this.query(new_query);
}
});
The convenience constructor for manual filters.
PourOver.makeManualFilter = function(name){
var filter = new PourOver.manualFilter(name,[]);
return filter;
}
An exact filter is the most commonly used filter. Given an attribute and a list of possibilities, an exact filter will bucket the items into those satisfying each of the possibilities. This also has the fastest performance as far as creating and updating.
PourOver.exactFilter = PourOver.Filter.extend({
cacheResults: PourOver.cacheMethods.exactCache,
addCacheResults: PourOver.cacheMethods.exactAddCache,
getFn: function(query){
var that = this;
if(_(query).isArray()){
var match_set = _(query).reduce(function(m,i){
if(!m){
return that.getFn(i);
} else {
return m.or(that.getFn(i));
}
},false);
return match_set;
} else {
var possibility = this.possibilities[query];
if (_.isUndefined(possibility) ) throw "The filter " + this.name + " does not have a match for the query '" + query + "'.";
return new PourOver.MatchSet(possibility.matching_cids,this.getCollection(),[[this,query]]);
}
}
});
The convenience constructor for exact filters.
PourOver.makeExactFilter = function(name,values,opts){
if(typeof(opts) === "undefined"){opts = {}}
var values = _(values).map(function(i){return {value:i}}),
opts = _.extend({associated_attrs: [name]},opts),
filter = new PourOver.exactFilter(name,values,opts);
return filter;
}
A range filter is for dividing items into buckets of ranges based on a specific attribute. A good example is, say, each item as 0-1000 "friends", then you can supply a range filter with the possibilities: [[0,10],[11,100],[101,1000]] and it will create buckets for 0-10, 11-100, and 101 + friends.
PourOver.rangeFilter = PourOver.Filter.extend({
cacheResults: PourOver.cacheMethods.defaultCache,
addCacheResults: PourOver.cacheMethods.defaultAddCache,
fn: function(possibility,item){
return possibility.low <= item[this.name] && possibility.high >= item[this.name]
},
getFn: function(query){
var possibility = this.possibilities[query.join("-")];
if (_.isUndefined(possibility) ) throw "The filter " + this.name + " does not have a match for the query '" + query + "'.";
return new PourOver.MatchSet(possibility.matching_cids,this.getCollection(),[[this,query]]);
}
});
The convenience constructor for range filters.
PourOver.makeRangeFilter = function(name,ranges,opts){
if(typeof(opts) === "undefined"){opts = {}}
var values = _(ranges).map(function(r){return {low: r[0], high: r[1], value: r.join("-")}}),
newopts = _.extend({associated_attrs: [name]},opts),
filter = new PourOver.rangeFilter(name,values,newopts);
return filter;
}
The inverse of a range filter. Again each item has single value for a certain attribute, but the possibilities you provide are every value of that attribute. Then, you query by a range. So, if a person can have 1-10 hats, you would feed a dv range filter the possibilities [1,2,3,4,5,6,7,8,9,10] and then make queries such as [2,5] for 2-5 hats. Do not use this for huge ranges like 1-100. Use crossfilter or write some optimized way of doing this. PourOver is not optimized for that kind of continuous query.
PourOver.dvrangeFilter = PourOver.Filter.extend({
cacheResults: PourOver.cacheMethods.exactCache,
addCacheResults: PourOver.cacheMethods.exactAddCache,
getFn: function(query){
if(! query[0] || ! query[1]){
return new PourOver.MatchSet([],this.getCollection(),[[this,query]]);
}
var li,hi,that,possibilities,cids;
li = _(this.values).indexOf(query[0]);
hi = _(this.values).indexOf(query[1]);
that = this;
possibilities = _(this.values.slice(li,hi+1)).map(function(p){return that.possibilities[p]});
cids = _(possibilities).reduce(function(m,i){ return PourOver.union_sorted(m,i.matching_cids) },[]);
return new PourOver.MatchSet(cids,this.getCollection(),[[this,query]]);
}
});
The convenience constructor for dv range filters.
PourOver.makeDVrangeFilter = function(name,v,opts){
if(typeof(opts) === "undefined"){opts = {}}
var values = _(v).map(function(i){return {value:i}}),
newopts = _.extend({associated_attrs: [name]},opts),
filter = new PourOver.dvrangeFilter(name,values,newopts);
return filter
}
Sorts items based on an explicit ordering of values. This would be useful for, say, a slideshow in which the order of items has nothing to do with any of their filterable attributes. Comes with methods to reorganize the items in the sort.
PourOver.explicitSort = PourOver.Sort.extend({
fn: function(a,b){
var a_index = _(this.order).indexOf(a[this.attr]),
b_index = _(this.order).indexOf(b[this.attr]);
if(a_index === -1) {a_index = 1/0}
if(b_index === -1) {b_index = 1/0}
return a_index - b_index;
},
reset: function(items){
this.order = _(items).pluck(this.attr);
this.rebuild_sort();
},
Insert an item into the sort.
insert: function(items,index){
if(typeof(index) === "undefined"){var index = this.order.length}
if(! _(items).isArray()){items = [items]}
var new_order = _(items).pluck(this.attr),
args = [index,0].concat(new_order);
this.order.splice.apply(this.order,args);
this.rebuild_sort();
},
Remove an item from the sort.
remove: function(items){
if(! _(items).isArray()){items = [items]}
var attrs = _(items).pluck(this.attr);
this.order = _(this.order).difference(attrs);
this.rebuild_sort();
},
Move an item from one place to another in the sort.
move: function(items,index){
if(! _(items).isArray()){items = [items]}
var attrs = _(items).pluck(this.attr);
this.order = _(this.order).map(function(o){ return _(attrs).include(o) ? null : o });
this.insert(items,index);
this.order = _(this.order).compact();
}
});
The convenience constructor for an explicit sort.
PourOver.makeExplicitSort = function(name,collection,attr,order,opts){
var sort = new PourOver.explicitSort(name,opts);
sort.attr = attr;
sort.order = order;
return sort;
}
A simple select element is roughly equivalent to elements such as a checklist or a radio list. Items can only be unioned together. One or more selected.
PourOver.UI.SimpleSelectElement = PourOver.UI.Element.extend({
initialize: function(opts){
if(!opts.filter){throw "A simple select element must have a filter specified"}
this.filter = opts.filter;
},
getMatchSet: function(){
return this.filter.current_query;
},
getFilterState: function(){
var match_set = this.getMatchSet();
return this.getSimpleSelectState(match_set)
}
});
A dv range element can be used for a slider in which you set the low and high and the query corresponds to everything in between.
PourOver.UI.SimpleDVRangeElement = PourOver.UI.Element.extend({
initialize: function(opts){
if(!opts.filter){throw "A simple dv range element must have a filter specified"}
this.filter = opts.filter;
},
getMatchSet: function(){
return this.filter.current_query;
},
getFilterState: function(){
var match_set = this.getMatchSet();
return this.getSimpleRangeState(match_set)
}
});