Repository: hacke2/hacke2.github.io Branch: master Commit: 7acd627fb514 Files: 833 Total size: 8.2 MB Directory structure: gitextract_t3zun56q/ ├── .about.md.swp ├── .gitignore ├── .jshintrc ├── .project ├── 404.md ├── CNAME ├── Gemfile ├── Gruntfile.js ├── LICENSE ├── README.md ├── Rakefile.rb ├── _config.yml ├── _includes/ │ ├── browser-upgrade.html │ ├── disqus_comments.html │ ├── footer.html │ ├── head.html │ ├── navigation.html │ └── scripts.html ├── _layouts/ │ ├── page.html │ ├── post-index.html │ └── post.html ├── _posts/ │ ├── 2014-10-1-for-me-1.html │ ├── 2014-10-1-mongoose-populate.md │ ├── 2014-10-11-the-golden-key.md │ ├── 2014-10-14-links-share-2014-10-14.md │ ├── 2014-10-2-great-nodejs.md │ ├── 2014-10-20-tink-in-Ghostjs.md │ ├── 2014-10-22-html5-javascript-web-dep.md │ ├── 2014-10-29-2014-10-d2.md │ ├── 2014-10-3-node-westom-mina.md │ ├── 2014-10-31-es6-modules-today-with-6to5.md │ ├── 2014-11-10-javascript-aop.md │ ├── 2014-11-11-hello-mobile-aop.md │ ├── 2014-11-12-create-github-page.md │ ├── 2014-11-14-answer-how-to-prepare.md │ ├── 2014-11-18-about-responsive.md │ ├── 2014-11-30-gugong.md │ ├── 2014-11-8-arrow-functions-and-their-scope.md │ ├── 2014-12-2-fe-cut-image.md │ ├── 2014-12-8-my-university-experience.md │ ├── 2014-8-29-hello-memory-leak.md │ ├── 2014-8-29-web-development-process.md │ ├── 2014-8-31-javascript-quiz.md │ ├── 2014-9-1-cqut-paging.md │ ├── 2014-9-1-oschina-angularjs.md │ ├── 2014-9-10-level-img-change.md │ ├── 2014-9-11-nodeJS-char.md │ ├── 2014-9-12-css3-lenove.md │ ├── 2014-9-18-no-sleep.md │ ├── 2014-9-2-what-happens-when-you-type-in-a-url-in-browser.md │ ├── 2014-9-26-div-center.md │ ├── 2014-9-26-think-in-seo.md │ ├── 2014-9-28-gokk.md │ ├── 2014-9-30-nodeJS-sublime-3.md │ ├── 2014-9-4-javascript-seamless-handover.md │ ├── 2014-9-4-my-show-4-angularjs.md │ ├── 2015-1-13-what-you-need-to-know-about-block-scope-let.md │ ├── 2015-1-14-2015-plan.md │ ├── 2015-10-21-scroll-in-uc.md │ ├── 2015-10-9-javascript-iterables-and-iterators.md │ ├── 2015-11-17-use-node-build-static-blog.md │ ├── 2015-2-25-Variables-and-scoping-in-ECMAScript-6.md │ ├── 2015-3-13-anmi-strange-problem.md │ ├── 2015-3-20-css-multi-row-overflow.md │ ├── 2015-3-23-frame-animation.md │ ├── 2015-3-4-to-teacher.md │ ├── 2015-3-7-share-jsbin.md │ ├── 2015-5-18-how-i-m-using-es6-modules-in-production.md │ ├── 2015-9-16-express-node-act.md │ ├── 2016-1-1-review-2015.md │ ├── 2016-2-1-configuring-babel-6-for-node-js.md │ ├── 2016-6-19-think-in-react-redux-1.md │ └── 2017-01-22-review-2016.md ├── about.md ├── assets/ │ ├── assets/ │ │ └── js/ │ │ └── plugins/ │ │ ├── jquery.dlmenu.js │ │ ├── jquery.fitvids.js │ │ └── jquery.magnific-popup.js │ ├── css/ │ │ └── main.css │ ├── fonts/ │ │ └── FontAwesome.otf │ ├── js/ │ │ ├── _main.js │ │ └── plugins/ │ │ ├── jquery.dlmenu.js │ │ ├── jquery.fitvids.js │ │ ├── jquery.magnific-popup.js │ │ └── respond.js │ └── less/ │ ├── coderay.less │ ├── dl-menu.less │ ├── elements.less │ ├── font-awesome/ │ │ ├── bordered-pulled.less │ │ ├── core.less │ │ ├── fixed-width.less │ │ ├── font-awesome.less │ │ ├── icons.less │ │ ├── larger.less │ │ ├── list.less │ │ ├── mixins.less │ │ ├── path.less │ │ ├── rotated-flipped.less │ │ ├── spinning.less │ │ ├── stacked.less │ │ └── variables.less │ ├── font-awesome.less │ ├── gist.less │ ├── grid.less │ ├── magnific-popup.less │ ├── main.less │ ├── mixins.less │ ├── page.less │ ├── pygments.less │ ├── reset.less │ ├── site.less │ ├── typography.less │ └── variables.less ├── common/ │ └── js/ │ ├── ajax.js │ ├── startmove.js │ └── test.js ├── demo/ │ ├── 2014-11/ │ │ ├── ali-media.html │ │ ├── ali-percent.html │ │ ├── ali-px.html │ │ └── ali-rem.html │ ├── absolute_percent.html │ ├── cqut-paging/ │ │ └── demo.html │ ├── css3/ │ │ └── imocoo_2.html │ ├── javascript-seamless-handover/ │ │ ├── css/ │ │ │ ├── main.css │ │ │ └── reset.css │ │ ├── index.html │ │ └── slide/ │ │ ├── bd01.html │ │ ├── css/ │ │ │ └── slide.css │ │ └── js/ │ │ └── startmove.js │ ├── js-template/ │ │ └── demo.html │ ├── mobile/ │ │ └── position/ │ │ ├── absolute.html │ │ ├── flex.html │ │ └── reset.css │ └── my-show-4-angularjs/ │ ├── index.html │ ├── index.js │ ├── js/ │ │ ├── angular-1.3.0.js │ │ ├── angular-animate.js │ │ ├── angular-route.js │ │ ├── angular-ui-router.js │ │ └── ui-bootstrap-tpls-0.11.0.js │ └── pages/ │ ├── about.html │ ├── contact.html │ ├── home.html │ └── project.html ├── feed.xml ├── index.html ├── monthly/ │ └── index.html ├── package.json ├── posts.html ├── ppt/ │ ├── css/ │ │ ├── font-awesome.css │ │ ├── nodeppt.css │ │ ├── pdf.css │ │ ├── pen.cur │ │ ├── phone.css │ │ ├── theme.blue.css │ │ ├── theme.dark.css │ │ ├── theme.green.css │ │ ├── theme.light.css │ │ └── theme.moon.css │ ├── express.html │ ├── fonts/ │ │ ├── .font-spider/ │ │ │ └── fontawesome-webfont │ │ └── FontAwesome.otf │ ├── js/ │ │ ├── event/ │ │ │ └── broadcast.js │ │ ├── highlight/ │ │ │ ├── default.css │ │ │ ├── highlight.pack.js │ │ │ ├── hljs-0.8.js │ │ │ ├── languages/ │ │ │ │ ├── 1c.js │ │ │ │ ├── actionscript.js │ │ │ │ ├── apache.js │ │ │ │ ├── applescript.js │ │ │ │ ├── asciidoc.js │ │ │ │ ├── autohotkey.js │ │ │ │ ├── avrasm.js │ │ │ │ ├── axapta.js │ │ │ │ ├── bash.js │ │ │ │ ├── brainfuck.js │ │ │ │ ├── capnproto.js │ │ │ │ ├── clojure.js │ │ │ │ ├── cmake.js │ │ │ │ ├── coffeescript.js │ │ │ │ ├── cpp.js │ │ │ │ ├── cs.js │ │ │ │ ├── css.js │ │ │ │ ├── d.js │ │ │ │ ├── delphi.js │ │ │ │ ├── diff.js │ │ │ │ ├── django.js │ │ │ │ ├── dos.js │ │ │ │ ├── elixir.js │ │ │ │ ├── erlang-repl.js │ │ │ │ ├── erlang.js │ │ │ │ ├── fix.js │ │ │ │ ├── fsharp.js │ │ │ │ ├── gherkin.js │ │ │ │ ├── glsl.js │ │ │ │ ├── go.js │ │ │ │ ├── gradle.js │ │ │ │ ├── haml.js │ │ │ │ ├── handlebars.js │ │ │ │ ├── haskell.js │ │ │ │ ├── haxe.js │ │ │ │ ├── http.js │ │ │ │ ├── ini.js │ │ │ │ ├── java.js │ │ │ │ ├── javascript.js │ │ │ │ ├── json.js │ │ │ │ ├── lasso.js │ │ │ │ ├── lisp.js │ │ │ │ ├── livecodeserver.js │ │ │ │ ├── lua.js │ │ │ │ ├── makefile.js │ │ │ │ ├── markdown.js │ │ │ │ ├── mathematica.js │ │ │ │ ├── matlab.js │ │ │ │ ├── mel.js │ │ │ │ ├── mizar.js │ │ │ │ ├── monkey.js │ │ │ │ ├── nginx.js │ │ │ │ ├── nimrod.js │ │ │ │ ├── nix.js │ │ │ │ ├── nsis.js │ │ │ │ ├── objectivec.js │ │ │ │ ├── ocaml.js │ │ │ │ ├── oxygene.js │ │ │ │ ├── parser3.js │ │ │ │ ├── perl.js │ │ │ │ ├── php.js │ │ │ │ ├── profile.js │ │ │ │ ├── protobuf.js │ │ │ │ ├── python.js │ │ │ │ ├── r.js │ │ │ │ ├── rib.js │ │ │ │ ├── rsl.js │ │ │ │ ├── ruby.js │ │ │ │ ├── ruleslanguage.js │ │ │ │ ├── rust.js │ │ │ │ ├── scala.js │ │ │ │ ├── scilab.js │ │ │ │ ├── scss.js │ │ │ │ ├── smalltalk.js │ │ │ │ ├── sql.js │ │ │ │ ├── swift.js │ │ │ │ ├── tex.js │ │ │ │ ├── thrift.js │ │ │ │ ├── typescript.js │ │ │ │ ├── vala.js │ │ │ │ ├── vbnet.js │ │ │ │ ├── vbscript.js │ │ │ │ ├── vhdl.js │ │ │ │ ├── vim.js │ │ │ │ ├── x86asm.js │ │ │ │ └── xml.js │ │ │ └── styles/ │ │ │ ├── arta.css │ │ │ ├── ascetic.css │ │ │ ├── atelier-dune.dark.css │ │ │ ├── atelier-dune.light.css │ │ │ ├── atelier-forest.dark.css │ │ │ ├── atelier-forest.light.css │ │ │ ├── atelier-heath.dark.css │ │ │ ├── atelier-heath.light.css │ │ │ ├── atelier-lakeside.dark.css │ │ │ ├── atelier-lakeside.light.css │ │ │ ├── atelier-seaside.dark.css │ │ │ ├── atelier-seaside.light.css │ │ │ ├── brown_paper.css │ │ │ ├── codepen-embed.css │ │ │ ├── color-brewer.css │ │ │ ├── dark.css │ │ │ ├── default.css │ │ │ ├── docco.css │ │ │ ├── far.css │ │ │ ├── foundation.css │ │ │ ├── github.css │ │ │ ├── googlecode.css │ │ │ ├── hybrid.css │ │ │ ├── idea.css │ │ │ ├── ir_black.css │ │ │ ├── kimbie.dark.css │ │ │ ├── kimbie.light.css │ │ │ ├── magula.css │ │ │ ├── mono-blue.css │ │ │ ├── monokai.css │ │ │ ├── monokai_sublime.css │ │ │ ├── obsidian.css │ │ │ ├── paraiso.dark.css │ │ │ ├── paraiso.light.css │ │ │ ├── pojoaque.css │ │ │ ├── railscasts.css │ │ │ ├── rainbow.css │ │ │ ├── school_book.css │ │ │ ├── solarized_dark.css │ │ │ ├── solarized_light.css │ │ │ ├── sunburst.css │ │ │ ├── tomorrow-night-blue.css │ │ │ ├── tomorrow-night-bright.css │ │ │ ├── tomorrow-night-eighties.css │ │ │ ├── tomorrow-night.css │ │ │ ├── tomorrow.css │ │ │ ├── vs.css │ │ │ ├── xcode.css │ │ │ └── zenburn.css │ │ ├── img.screenfull.js │ │ ├── mixjs/ │ │ │ ├── .npmignore │ │ │ ├── Gruntfile.js │ │ │ ├── MIT-LICENSE.txt │ │ │ ├── README.md │ │ │ ├── lib/ │ │ │ │ ├── event/ │ │ │ │ │ ├── broadcast.js │ │ │ │ │ └── wait.js │ │ │ │ ├── mix-0.2.js │ │ │ │ └── mix.0.3.0.js │ │ │ ├── package.json │ │ │ ├── research/ │ │ │ │ ├── js.php │ │ │ │ ├── load-test.html │ │ │ │ ├── loadjs.html │ │ │ │ └── userAgent.html │ │ │ └── src/ │ │ │ ├── Module.js │ │ │ ├── Promise.js │ │ │ ├── browser.js │ │ │ ├── core.js │ │ │ ├── getCurrentScript.js │ │ │ ├── getPath.js │ │ │ ├── intro.js │ │ │ ├── loadcss-img.js │ │ │ ├── loadcss.js │ │ │ ├── loadjs.js │ │ │ ├── outro.js │ │ │ ├── typeof.js │ │ │ └── vars.js │ │ ├── nodeppt.control.js │ │ ├── nodeppt.control.postMessage.js │ │ ├── nodeppt.control.socket.js │ │ ├── nodeppt.js │ │ ├── prettify.js │ │ ├── qrcode.js │ │ ├── shake.js │ │ ├── socket.io.js │ │ └── zoom.js │ ├── node-generator-koa/ │ │ ├── css/ │ │ │ ├── animation.css │ │ │ ├── font-awesome.css │ │ │ ├── nodeppt.css │ │ │ ├── nodeppt2.0.css │ │ │ ├── pdf.css │ │ │ ├── pen.cur │ │ │ ├── phone.css │ │ │ ├── theme.blue.css │ │ │ ├── theme.dark.css │ │ │ ├── theme.green.css │ │ │ ├── theme.light.css │ │ │ └── theme.moon.css │ │ ├── fonts/ │ │ │ └── FontAwesome.otf │ │ ├── index.html │ │ └── js/ │ │ ├── event/ │ │ │ └── broadcast.js │ │ ├── highlight/ │ │ │ ├── default.css │ │ │ ├── highlight.pack.js │ │ │ ├── hljs-0.8.js │ │ │ ├── languages/ │ │ │ │ ├── 1c.js │ │ │ │ ├── actionscript.js │ │ │ │ ├── apache.js │ │ │ │ ├── applescript.js │ │ │ │ ├── asciidoc.js │ │ │ │ ├── autohotkey.js │ │ │ │ ├── avrasm.js │ │ │ │ ├── axapta.js │ │ │ │ ├── bash.js │ │ │ │ ├── brainfuck.js │ │ │ │ ├── capnproto.js │ │ │ │ ├── clojure.js │ │ │ │ ├── cmake.js │ │ │ │ ├── coffeescript.js │ │ │ │ ├── cpp.js │ │ │ │ ├── cs.js │ │ │ │ ├── css.js │ │ │ │ ├── d.js │ │ │ │ ├── delphi.js │ │ │ │ ├── diff.js │ │ │ │ ├── django.js │ │ │ │ ├── dos.js │ │ │ │ ├── elixir.js │ │ │ │ ├── erlang-repl.js │ │ │ │ ├── erlang.js │ │ │ │ ├── fix.js │ │ │ │ ├── fsharp.js │ │ │ │ ├── gherkin.js │ │ │ │ ├── glsl.js │ │ │ │ ├── go.js │ │ │ │ ├── gradle.js │ │ │ │ ├── haml.js │ │ │ │ ├── handlebars.js │ │ │ │ ├── haskell.js │ │ │ │ ├── haxe.js │ │ │ │ ├── http.js │ │ │ │ ├── ini.js │ │ │ │ ├── java.js │ │ │ │ ├── javascript.js │ │ │ │ ├── json.js │ │ │ │ ├── lasso.js │ │ │ │ ├── lisp.js │ │ │ │ ├── livecodeserver.js │ │ │ │ ├── lua.js │ │ │ │ ├── makefile.js │ │ │ │ ├── markdown.js │ │ │ │ ├── mathematica.js │ │ │ │ ├── matlab.js │ │ │ │ ├── mel.js │ │ │ │ ├── mizar.js │ │ │ │ ├── monkey.js │ │ │ │ ├── nginx.js │ │ │ │ ├── nimrod.js │ │ │ │ ├── nix.js │ │ │ │ ├── nsis.js │ │ │ │ ├── objectivec.js │ │ │ │ ├── ocaml.js │ │ │ │ ├── oxygene.js │ │ │ │ ├── parser3.js │ │ │ │ ├── perl.js │ │ │ │ ├── php.js │ │ │ │ ├── profile.js │ │ │ │ ├── protobuf.js │ │ │ │ ├── python.js │ │ │ │ ├── r.js │ │ │ │ ├── rib.js │ │ │ │ ├── rsl.js │ │ │ │ ├── ruby.js │ │ │ │ ├── ruleslanguage.js │ │ │ │ ├── rust.js │ │ │ │ ├── scala.js │ │ │ │ ├── scilab.js │ │ │ │ ├── scss.js │ │ │ │ ├── smalltalk.js │ │ │ │ ├── sql.js │ │ │ │ ├── swift.js │ │ │ │ ├── tex.js │ │ │ │ ├── thrift.js │ │ │ │ ├── typescript.js │ │ │ │ ├── vala.js │ │ │ │ ├── vbnet.js │ │ │ │ ├── vbscript.js │ │ │ │ ├── vhdl.js │ │ │ │ ├── vim.js │ │ │ │ ├── x86asm.js │ │ │ │ └── xml.js │ │ │ └── styles/ │ │ │ ├── arta.css │ │ │ ├── ascetic.css │ │ │ ├── atelier-dune.dark.css │ │ │ ├── atelier-dune.light.css │ │ │ ├── atelier-forest.dark.css │ │ │ ├── atelier-forest.light.css │ │ │ ├── atelier-heath.dark.css │ │ │ ├── atelier-heath.light.css │ │ │ ├── atelier-lakeside.dark.css │ │ │ ├── atelier-lakeside.light.css │ │ │ ├── atelier-seaside.dark.css │ │ │ ├── atelier-seaside.light.css │ │ │ ├── brown_paper.css │ │ │ ├── codepen-embed.css │ │ │ ├── color-brewer.css │ │ │ ├── dark.css │ │ │ ├── default.css │ │ │ ├── docco.css │ │ │ ├── far.css │ │ │ ├── foundation.css │ │ │ ├── github.css │ │ │ ├── googlecode.css │ │ │ ├── hybrid.css │ │ │ ├── idea.css │ │ │ ├── ir_black.css │ │ │ ├── kimbie.dark.css │ │ │ ├── kimbie.light.css │ │ │ ├── magula.css │ │ │ ├── mono-blue.css │ │ │ ├── monokai.css │ │ │ ├── monokai_sublime.css │ │ │ ├── obsidian.css │ │ │ ├── paraiso.dark.css │ │ │ ├── paraiso.light.css │ │ │ ├── pojoaque.css │ │ │ ├── railscasts.css │ │ │ ├── rainbow.css │ │ │ ├── school_book.css │ │ │ ├── solarized_dark.css │ │ │ ├── solarized_light.css │ │ │ ├── sunburst.css │ │ │ ├── tomorrow-night-blue.css │ │ │ ├── tomorrow-night-bright.css │ │ │ ├── tomorrow-night-eighties.css │ │ │ ├── tomorrow-night.css │ │ │ ├── tomorrow.css │ │ │ ├── vs.css │ │ │ ├── xcode.css │ │ │ └── zenburn.css │ │ ├── img.screenfull.js │ │ ├── mixjs/ │ │ │ ├── .npmignore │ │ │ ├── Gruntfile.js │ │ │ ├── MIT-LICENSE.txt │ │ │ ├── README.md │ │ │ ├── lib/ │ │ │ │ ├── event/ │ │ │ │ │ ├── broadcast.js │ │ │ │ │ └── wait.js │ │ │ │ ├── mix-0.2.js │ │ │ │ └── mix.0.3.0.js │ │ │ ├── package.json │ │ │ ├── research/ │ │ │ │ ├── js.php │ │ │ │ ├── load-test.html │ │ │ │ ├── loadjs.html │ │ │ │ └── userAgent.html │ │ │ └── src/ │ │ │ ├── Module.js │ │ │ ├── Promise.js │ │ │ ├── browser.js │ │ │ ├── core.js │ │ │ ├── getCurrentScript.js │ │ │ ├── getPath.js │ │ │ ├── intro.js │ │ │ ├── loadcss-img.js │ │ │ ├── loadcss.js │ │ │ ├── loadjs.js │ │ │ ├── outro.js │ │ │ ├── typeof.js │ │ │ └── vars.js │ │ ├── nodeppt.control.js │ │ ├── nodeppt.control.postMessage.js │ │ ├── nodeppt.control.socket.js │ │ ├── nodeppt.js │ │ ├── prettify.js │ │ ├── qrcode.js │ │ ├── shake.js │ │ └── socket.io.js │ └── think-in-css/ │ ├── css/ │ │ ├── font-awesome.css │ │ ├── nodeppt.css │ │ ├── pdf.css │ │ ├── pen.cur │ │ ├── phone.css │ │ ├── theme.blue.css │ │ ├── theme.dark.css │ │ ├── theme.green.css │ │ ├── theme.light.css │ │ └── theme.moon.css │ ├── fonts/ │ │ ├── .font-spider/ │ │ │ └── fontawesome-webfont │ │ └── FontAwesome.otf │ ├── index.html │ ├── js/ │ │ ├── event/ │ │ │ └── broadcast.js │ │ ├── highlight/ │ │ │ ├── default.css │ │ │ ├── highlight.pack.js │ │ │ ├── hljs-0.8.js │ │ │ ├── languages/ │ │ │ │ ├── 1c.js │ │ │ │ ├── actionscript.js │ │ │ │ ├── apache.js │ │ │ │ ├── applescript.js │ │ │ │ ├── asciidoc.js │ │ │ │ ├── autohotkey.js │ │ │ │ ├── avrasm.js │ │ │ │ ├── axapta.js │ │ │ │ ├── bash.js │ │ │ │ ├── brainfuck.js │ │ │ │ ├── capnproto.js │ │ │ │ ├── clojure.js │ │ │ │ ├── cmake.js │ │ │ │ ├── coffeescript.js │ │ │ │ ├── cpp.js │ │ │ │ ├── cs.js │ │ │ │ ├── css.js │ │ │ │ ├── d.js │ │ │ │ ├── delphi.js │ │ │ │ ├── diff.js │ │ │ │ ├── django.js │ │ │ │ ├── dos.js │ │ │ │ ├── elixir.js │ │ │ │ ├── erlang-repl.js │ │ │ │ ├── erlang.js │ │ │ │ ├── fix.js │ │ │ │ ├── fsharp.js │ │ │ │ ├── gherkin.js │ │ │ │ ├── glsl.js │ │ │ │ ├── go.js │ │ │ │ ├── gradle.js │ │ │ │ ├── haml.js │ │ │ │ ├── handlebars.js │ │ │ │ ├── haskell.js │ │ │ │ ├── haxe.js │ │ │ │ ├── http.js │ │ │ │ ├── ini.js │ │ │ │ ├── java.js │ │ │ │ ├── javascript.js │ │ │ │ ├── json.js │ │ │ │ ├── lasso.js │ │ │ │ ├── lisp.js │ │ │ │ ├── livecodeserver.js │ │ │ │ ├── lua.js │ │ │ │ ├── makefile.js │ │ │ │ ├── markdown.js │ │ │ │ ├── mathematica.js │ │ │ │ ├── matlab.js │ │ │ │ ├── mel.js │ │ │ │ ├── mizar.js │ │ │ │ ├── monkey.js │ │ │ │ ├── nginx.js │ │ │ │ ├── nimrod.js │ │ │ │ ├── nix.js │ │ │ │ ├── nsis.js │ │ │ │ ├── objectivec.js │ │ │ │ ├── ocaml.js │ │ │ │ ├── oxygene.js │ │ │ │ ├── parser3.js │ │ │ │ ├── perl.js │ │ │ │ ├── php.js │ │ │ │ ├── profile.js │ │ │ │ ├── protobuf.js │ │ │ │ ├── python.js │ │ │ │ ├── r.js │ │ │ │ ├── rib.js │ │ │ │ ├── rsl.js │ │ │ │ ├── ruby.js │ │ │ │ ├── ruleslanguage.js │ │ │ │ ├── rust.js │ │ │ │ ├── scala.js │ │ │ │ ├── scilab.js │ │ │ │ ├── scss.js │ │ │ │ ├── smalltalk.js │ │ │ │ ├── sql.js │ │ │ │ ├── swift.js │ │ │ │ ├── tex.js │ │ │ │ ├── thrift.js │ │ │ │ ├── typescript.js │ │ │ │ ├── vala.js │ │ │ │ ├── vbnet.js │ │ │ │ ├── vbscript.js │ │ │ │ ├── vhdl.js │ │ │ │ ├── vim.js │ │ │ │ ├── x86asm.js │ │ │ │ └── xml.js │ │ │ └── styles/ │ │ │ ├── arta.css │ │ │ ├── ascetic.css │ │ │ ├── atelier-dune.dark.css │ │ │ ├── atelier-dune.light.css │ │ │ ├── atelier-forest.dark.css │ │ │ ├── atelier-forest.light.css │ │ │ ├── atelier-heath.dark.css │ │ │ ├── atelier-heath.light.css │ │ │ ├── atelier-lakeside.dark.css │ │ │ ├── atelier-lakeside.light.css │ │ │ ├── atelier-seaside.dark.css │ │ │ ├── atelier-seaside.light.css │ │ │ ├── brown_paper.css │ │ │ ├── codepen-embed.css │ │ │ ├── color-brewer.css │ │ │ ├── dark.css │ │ │ ├── default.css │ │ │ ├── docco.css │ │ │ ├── far.css │ │ │ ├── foundation.css │ │ │ ├── github.css │ │ │ ├── googlecode.css │ │ │ ├── hybrid.css │ │ │ ├── idea.css │ │ │ ├── ir_black.css │ │ │ ├── kimbie.dark.css │ │ │ ├── kimbie.light.css │ │ │ ├── magula.css │ │ │ ├── mono-blue.css │ │ │ ├── monokai.css │ │ │ ├── monokai_sublime.css │ │ │ ├── obsidian.css │ │ │ ├── paraiso.dark.css │ │ │ ├── paraiso.light.css │ │ │ ├── pojoaque.css │ │ │ ├── railscasts.css │ │ │ ├── rainbow.css │ │ │ ├── school_book.css │ │ │ ├── solarized_dark.css │ │ │ ├── solarized_light.css │ │ │ ├── sunburst.css │ │ │ ├── tomorrow-night-blue.css │ │ │ ├── tomorrow-night-bright.css │ │ │ ├── tomorrow-night-eighties.css │ │ │ ├── tomorrow-night.css │ │ │ ├── tomorrow.css │ │ │ ├── vs.css │ │ │ ├── xcode.css │ │ │ └── zenburn.css │ │ ├── img.screenfull.js │ │ ├── mixjs/ │ │ │ ├── .npmignore │ │ │ ├── Gruntfile.js │ │ │ ├── MIT-LICENSE.txt │ │ │ ├── README.md │ │ │ ├── lib/ │ │ │ │ ├── event/ │ │ │ │ │ ├── broadcast.js │ │ │ │ │ └── wait.js │ │ │ │ ├── mix-0.2.js │ │ │ │ └── mix.0.3.0.js │ │ │ ├── package.json │ │ │ ├── research/ │ │ │ │ ├── js.php │ │ │ │ ├── load-test.html │ │ │ │ ├── loadjs.html │ │ │ │ └── userAgent.html │ │ │ └── src/ │ │ │ ├── Module.js │ │ │ ├── Promise.js │ │ │ ├── browser.js │ │ │ ├── core.js │ │ │ ├── getCurrentScript.js │ │ │ ├── getPath.js │ │ │ ├── intro.js │ │ │ ├── loadcss-img.js │ │ │ ├── loadcss.js │ │ │ ├── loadjs.js │ │ │ ├── outro.js │ │ │ ├── typeof.js │ │ │ └── vars.js │ │ ├── nodeppt.control.js │ │ ├── nodeppt.control.postMessage.js │ │ ├── nodeppt.control.socket.js │ │ ├── nodeppt.js │ │ ├── prettify.js │ │ ├── qrcode.js │ │ ├── shake.js │ │ ├── socket.io.js │ │ └── zoom.js │ ├── sm.htm │ └── think-in-css.html ├── project-school/ │ ├── .project │ ├── html/ │ │ ├── resume.html │ │ ├── template.html │ │ └── test.txt │ ├── index.gtml │ ├── index.html │ ├── js/ │ │ ├── angular.js │ │ ├── controllers.js │ │ ├── index.js │ │ └── tool.js │ └── themes/ │ ├── default/ │ │ ├── accordion.css │ │ ├── calendar.css │ │ ├── combo.css │ │ ├── combobox.css │ │ ├── datagrid.css │ │ ├── datebox.css │ │ ├── dialog.css │ │ ├── easyui.css │ │ ├── layout.css │ │ ├── linkbutton.css │ │ ├── menu.css │ │ ├── menubutton.css │ │ ├── messager.css │ │ ├── pagination.css │ │ ├── panel.css │ │ ├── progressbar.css │ │ ├── propertygrid.css │ │ ├── searchbox.css │ │ ├── slider.css │ │ ├── spinner.css │ │ ├── splitbutton.css │ │ ├── tabs.css │ │ ├── tooltip.css │ │ ├── tree.css │ │ ├── validatebox.css │ │ └── window.css │ └── icon.css ├── sitemap.xml ├── tags.html ├── theme-setup.md └── works/ ├── css/ │ ├── common.css │ └── home.css ├── demo/ │ ├── 03/ │ │ ├── bd03.html │ │ └── css/ │ │ └── main.css │ ├── 04/ │ │ ├── css/ │ │ │ ├── main.css │ │ │ └── r.css │ │ ├── data.js │ │ └── index.html │ ├── 06/ │ │ ├── css/ │ │ │ └── main.css │ │ └── index.html │ ├── 07/ │ │ ├── css/ │ │ │ └── main.css │ │ └── index.html │ ├── 08/ │ │ ├── css/ │ │ │ └── main.css │ │ └── index.html │ ├── ali/ │ │ ├── actor.html │ │ ├── file/ │ │ │ ├── abtest1120.css │ │ │ ├── ac_base.js │ │ │ ├── ac_retina.js │ │ │ ├── aliyun_core.js │ │ │ ├── aplus_v2.js │ │ │ ├── base.css │ │ │ ├── browserdetect.js │ │ │ ├── btm.v.3.css │ │ │ ├── event_mixins.js │ │ │ ├── header_footer.css │ │ │ ├── index.js │ │ │ ├── index_2.css │ │ │ ├── index_3.css │ │ │ ├── jquery.fullbg.js │ │ │ ├── organictabs.jquery.js │ │ │ ├── overview.css │ │ │ ├── overview.js │ │ │ ├── owl.carousel.js │ │ │ ├── pb_v.0.0.js │ │ │ ├── prototype.js │ │ │ ├── reveal.css │ │ │ ├── s_code_h.js │ │ │ ├── saved_resource │ │ │ ├── sizzle.js │ │ │ ├── swap_view.js │ │ │ └── yun4.htm │ │ └── startmove.js │ ├── d3/ │ │ ├── d3.v3.js │ │ └── 新建文本文档.html │ ├── ee/ │ │ ├── index.htm │ │ └── index_files/ │ │ ├── analytics.js │ │ ├── backgroundClasses.css │ │ ├── dataset-shim.js │ │ ├── default-reset.css │ │ ├── default.css │ │ ├── impress.js │ │ ├── loadPresentation.js │ │ ├── main.css │ │ ├── onready.js │ │ ├── surfaceClasses.css │ │ └── web-fonts.css │ ├── html5/ │ │ └── ali.html │ ├── qqzone-img/ │ │ └── index.html │ ├── sm-meituan/ │ │ ├── index.html │ │ └── main.css │ └── stream/ │ ├── data.js │ ├── data2.js │ ├── float.html │ └── position.html ├── index.html ├── index_v1.html ├── index_v2.html ├── js/ │ ├── data.js │ ├── index.js │ └── lib/ │ ├── angular-1.3.0.js │ ├── angular-animate.js │ ├── angular-route.js │ ├── angular-ui-router.js │ ├── jquery.js │ └── ui-bootstrap-tpls-0.11.0.js ├── main.html └── template/ ├── about.html ├── show-m.html └── show.html ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ _site .DS_Store *.sublime-project *.sublime-workspace codekit-config.json node_modules Gemfile.lock ================================================ FILE: .jshintrc ================================================ { "bitwise": true, "browser": true, "curly": true, "eqeqeq": true, "eqnull": true, "es5": false, "esnext": true, "immed": true, "jquery": true, "latedef": true, "newcap": true, "noarg": true, "node": true, "strict": false, "trailing": false, "undef": true, "multistr": true, "expr": true } ================================================ FILE: .project ================================================ github_blog com.aptana.ide.core.unifiedBuilder com.aptana.projects.webnature ================================================ FILE: 404.md ================================================ --- layout: page title: "Page Not Found" description: "Page not found. Your pixels are in another canvas." --- Sorry, but the page you were trying to view does not exist --- perhaps you can try searching for it below. ================================================ FILE: CNAME ================================================ ================================================ FILE: Gemfile ================================================ source 'https://rubygems.org' gem 'jekyll' gem 'kramdown' gem 'coderay' gem 'rake' gem 'thor' gem 'activesupport' gem 'stringex' ================================================ FILE: Gruntfile.js ================================================ 'use strict'; module.exports = function(grunt) { grunt.initConfig({ jshint: { options: { jshintrc: '.jshintrc' }, all: [ 'Gruntfile.js', 'assets/js/*.js', 'assets/js/plugins/*.js', '!assets/js/scripts.min.js' ] }, recess: { dist: { options: { compile: true, compress: true }, files: { 'assets/css/main.min.css': [ 'assets/less/main.less' ] } }, dev: { options: { compile: true, compress: false }, files: { 'assets/css/main.css': [ 'assets/less/main.less' ] } } }, uglify: { dist: { files: { 'assets/js/scripts.min.js': [ 'assets/js/plugins/*.js', 'assets/js/_*.js' ] } } }, imagemin: { dist: { options: { optimizationLevel: 7, progressive: true }, files: [{ expand: true, cwd: 'images/', src: '{,*/}*.{png,jpg,jpeg}', dest: 'images/' }] } }, svgmin: { dist: { files: [{ expand: true, cwd: 'images/', src: '{,*/}*.svg', dest: 'images/' }] } }, watch: { less: { files: [ 'assets/less/*.less', 'assets/less/bootstrap/*.less' ], tasks: ['recess'] }, js: { files: [ '<%= jshint.all %>' ], tasks: ['jshint','uglify'] } }, clean: { dist: [ 'assets/css/main.min.css', 'assets/js/scripts.min.js' ] } }); // Load tasks grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-recess'); grunt.loadNpmTasks('grunt-contrib-imagemin'); grunt.loadNpmTasks('grunt-svgmin'); // Register tasks grunt.registerTask('default', [ 'clean', 'recess', 'uglify', 'imagemin', 'svgmin' ]); grunt.registerTask('dev', [ 'watch' ]); }; ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2014 Michael Rose 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. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 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. ================================================ FILE: README.md ================================================ 个人前端博客 ---------- ## [点此访问 www.hacke2.cn](http://www.hacke2.cn) 本博客是基于[hpstr jekyll]("https://github.com/hacke2/hpstr-jekyll-theme)而搭建的个人博客,在原有强大的功能上,做了如下修改: * 将google cdn换成 baidu cdn 原因你懂的 * 去掉分享到twitter、facebook等国外社区,加入百度分享 * 加入百度站长助手,方便您的统计 * disqus评论 * Read More功能,不想像以前一样文章全显示出来 如果您想快速搭建和我一样属于自己的博客系统,请参阅: [30秒创建Github Page](http://www.hacke2.cn/create-github-page/) 目前待优化事项: * 合并资源(JS,CSS) ================================================ FILE: Rakefile.rb ================================================ require "rubygems" require "bundler/setup" require "stringex" ## -- Config -- ## public_dir = "public" # compiled site directory posts_dir = "_posts" # directory for blog files new_post_ext = "md" # default new post file extension when using the new_post task new_page_ext = "md" # default new page file extension when using the new_page task ############################# # Create a new Post or Page # ############################# # usage rake new_post desc "Create a new post in #{posts_dir}" task :new_post, :title do |t, args| if args.title title = args.title else title = get_stdin("Enter a title for your post: ") end filename = "#{posts_dir}/#{Time.now.strftime('%Y-%m-%d')}-#{title.to_url}.#{new_post_ext}" if File.exist?(filename) abort("rake aborted!") if ask("#{filename} already exists. Do you want to overwrite?", ['y', 'n']) == 'n' end tags = get_stdin("Enter tags to classify your post (comma separated): ") puts "Creating new post: #{filename}" open(filename, 'w') do |post| post.puts "---" post.puts "layout: post" post.puts "title: \"#{title.gsub(/&/,'&')}\"" post.puts "modified: #{Time.now.strftime('%Y-%m-%d %H:%M:%S %z')}" post.puts "tags: [#{tags}]" post.puts "image:" post.puts " feature: " post.puts " credit: " post.puts " creditlink: " post.puts "comments: " post.puts "share: " post.puts "---" end end # usage rake new_page desc "Create a new page" task :new_page, :title do |t, args| if args.title title = args.title else title = get_stdin("Enter a title for your page: ") end filename = "#{title.to_url}.#{new_page_ext}" if File.exist?(filename) abort("rake aborted!") if ask("#{filename} already exists. Do you want to overwrite?", ['y', 'n']) == 'n' end tags = get_stdin("Enter tags to classify your page (comma separated): ") puts "Creating new page: #{filename}" open(filename, 'w') do |page| page.puts "---" page.puts "layout: page" page.puts "permalink: /#{title.to_url}/" page.puts "title: \"#{title}\"" page.puts "modified: #{Time.now.strftime('%Y-%m-%d %H:%M')}" page.puts "tags: [#{tags}]" page.puts "image:" page.puts " feature: " page.puts " credit: " page.puts " creditlink: " page.puts "share: " page.puts "---" end end def get_stdin(message) print message STDIN.gets.chomp end def ask(message, valid_options) if valid_options answer = get_stdin("#{message} #{valid_options.to_s.gsub(/"/, '').gsub(/, /,'/')} ") while !valid_options.include?(answer) else answer = get_stdin(message) end answer end ================================================ FILE: _config.yml ================================================ title: hacke2's blog description: hacke2的前端技术博客,分享自己的技术心得,积累前段技能,汇聚前端之路的点点滴滴。 disqus_shortname: hacke2 reading_time: true words_per_minute: 200 #Your site's domain goes here. Leave localhost server or blank when working locally. url: http://www.hacke2.cn # Owner/author information owner: name: hacke2 avatar: wxl-photo2.jpg bio: "梦想还是要有的,万一实现了呢?" email: hacke2@qq.com # Social networking links used in footer. Update and remove as you like. twitter: facebook: github: hacke2 stackexchange: linkedin: instagram: flickr: tumblr: # For Google Authorship https://plus.google.com/authorship # google plus id, include the '+', eg +mmistakes google_plus: +yourid # Background image to be tiled on all pages background: # Analytics and webmaster tools stuff goes here google_analytics: google_verify: # https://ssl.bing.com/webmaster/configure/verify/ownership Option 2 content= goes here bing_verify: # Links to include in top navigation # For external links add external: true links: # - title: Theme Setup # url: /theme-setup - title: Works url: http://www.hacke2.cn/works external: true # - title: GitHub # url: https://github.com/hacke2 # external: true # http://en.wikipedia.org/wiki/List_of_tz_database_time_zones timezone: America/New_York future: true highlighter: rouge markdown: kramdown # https://github.com/mojombo/jekyll/wiki/Permalinks permalink: /:categories/:title/ # Amount of post to show on home page paginate: 5 kramdown: auto_ids: true footnote_nr: 1 entity_output: as_char toc_levels: 1..6 use_coderay: true coderay: coderay_line_numbers: nil coderay_line_numbers_start: 1 coderay_tab_width: 4 coderay_bold_every: 10 coderay_css: class include: [".htaccess"] exclude: ["lib", "config.rb", "Capfile", "config", "Gemfile", "Gemfile.lock", "README.md", "LICENSE", "log", "Rakefile", "Rakefile.rb", "tmp", "less", "*.sublime-project", "*.sublime-workspace", "test", "spec", "Gruntfile.js", "package.json", "node_modules"] ================================================ FILE: _includes/browser-upgrade.html ================================================ ================================================ FILE: _includes/disqus_comments.html ================================================ comments powered by Disqus ================================================ FILE: _includes/footer.html ================================================ © {{ site.time | date: '%Y' }} {{ site.owner.name }}. Powered by Jekyll using the HPSTR Theme. ================================================ FILE: _includes/head.html ================================================ {% if page.title %}{{ page.title }} – {% endif %}{{ site.title }} {% if page.tags %}{% endif %} {% if site.owner.twitter %} {% if page.image.feature %} {% else %} {% endif %} {% endif %} {% if site.google_verify %}{% endif %} {% if site.bing_verify %}{% endif %} {% capture canonical %}{{ site.url }}{% if site.permalink contains '.html' %}{{ page.url }}{% else %}{{ page.url | remove:'index.html' | strip_slash }}{% endif %}{% endcapture %} {% if page.image.background or site.background %} {% capture background %}{% if page.image.background %}{{ page.image.background }}{% else %}{{ site.background }}{% endif %}{% endcapture %} {% unless background contains 'http://' or background contains 'https://' %}{% capture background %}{{ site.url }}/images/{{ background }}{% endcapture %}{% endunless %} {% endif %} ================================================ FILE: _includes/navigation.html ================================================ ================================================ FILE: _includes/scripts.html ================================================ {% if site.google_analytics %} {% endif %} {% if page.comments %}{% include disqus_comments.html %}{% endif %} ================================================ FILE: _layouts/page.html ================================================ {% include head.html %} {% include browser-upgrade.html %} {% include navigation.html %} {% if page.image.feature %}
{{ page.title }}
{% endif %}

{{ page.title }}

{% if site.reading_time %}

{% assign readtime = content | number_of_words | divided_by:site.words_per_minute %} Reading time ~{% if readtime <= 1 %}1 minute{% else %}{{ readtime }} minutes{% endif %}

{% endif %}
{{ content }}
{% if page.modified %}{{ page.title }} was last modified on {{ site.owner.name }}{% endif %} {% if page.share %}{% endif %}
{% if site.disqus_shortname and page.comments %}
{% endif %}
{% include scripts.html %} ================================================ FILE: _layouts/post-index.html ================================================ {% include head.html %} {% include browser-upgrade.html %} {% include navigation.html %}
{% if page.image.feature %}
{% endif %}

{{ site.title }}

{{ page.title }}

{{ content }}
{% include scripts.html %} ================================================ FILE: _layouts/post.html ================================================ {% include head.html %} {% include browser-upgrade.html %} {% include navigation.html %} {% if page.image.feature %}
{{ page.title }}
{% endif %}
{% if page.link %}

{{ page.title }}

{% else %}

{{ page.title }}

{% endif %}

{{ page.date | date: "%B %d, %Y" }}

{% if site.reading_time %}

{% assign readtime = content | number_of_words | divided_by:site.words_per_minute %} Reading time ~{% if readtime <= 1 %}1 minute{% else %}{{ readtime }} minutes{% endif %}

{% endif %}
{{ content }}
{{ page.title }} was published on {% if page.modified %}(revised: ){% endif %} {{ site.owner.name }} {% if page.share %}
{% endif %}
{% if site.disqus_shortname and page.comments %}
{% endif %} {% if site.related_posts.size %}
{% for post in site.related_posts limit:1 %}
Read More

{{ post.title }}

{% if post.description %}{{ post.description }}{% else %}{{ post.content | strip_html | strip_newlines | truncate: 140 }}…{% endif %} Continue reading

{% endfor %}
{% for post in site.related_posts limit:2 offset:1 %}

{{ post.title }}

Published on {{ post.date | date: "%B %d, %Y" }}
{% endfor %}
{% endif %}
{% include scripts.html %} ================================================ FILE: _posts/2014-10-1-for-me-1.html ================================================ 致自己

我就是想出来看看外面的世界

长图
================================================ FILE: _posts/2014-10-1-mongoose-populate.md ================================================ --- layout: post title: Mongoose简单的连表查询 description: "使用populate属性" tags: [Mongoose] image: background: witewall_3.png comments: true share: true --- 像我这篇文章所说的基于Node.js + jade + Mongoose 模仿gokk.tv,当时停止开发是因为我深深的感觉到当时想错了,应该用两个Schema,而不是一个下面又有数组来存,这样取数据是方便,当时分页相当麻烦,不能使用原生提供的limit方法。 今天看到一本书上有讲,尝试了一把,记录下来 我们实验的场景为一个班级有N多学生,先通过学生ID找到班级名称(是不是被玩腻了?) 先来将Schema定义好 ClazzSchema : ```js var mongoose = require('mongoose') var ClazzSchema = new mongoose.Schema({ clazzName:String }) //其他方法省略.. } module.exports = ClazzSchema ``` StudentSchema : ```js var mongoose = require('mongoose') var StudentSchema = new mongoose.Schema({ name:String, clazzID : { type : mongoose.Schema.ObjectId, ref : 'Clazz' } }) StudentSchema.statics = { findClazzNameByStudentId:function(studentId, callback){ return this .findOne({_id : studentId}).populate('clazzID') .exec(callback) } //其他方法省略.. } module.exports = StudentSchema ``` 可以看到,主需要将ClazzID设为ref到Clazz,依赖为你建立Model时的名称就可以了,要查询Clzz使用populate 下面是Model ```js var mongoose = require('mongoose') var ClazzSchema = require('../schemas/clazzSchema') var Clazz = mongoose.model('Clazz',ClazzSchema) module.exports = Clazz ``` ```js var mongoose = require('mongoose') var StudentSchema = require('../schemas/studentSchema') var Student = mongoose.model('Student',StudentSchema) module.exports = Student ``` 大同小异,着重看test.js ```js var mongoose = require('mongoose') var Clazz = require('./models/clazzModel') var Student = require('./models/studentModel') //var db = mongoose.createConnection('localhost', 'gokk') mongoose.connect('mongodb://localhost/test') /*var clazz = new Clazz( { clazzName:'软件2班' } ); clazz.save(function (argument){ console.log('true'); });*/ /*var student = new Student({ name : 'hacke2', clazzID : '542b5fcc49df6e741d8d15f5' }) student.save(function (err){ console.log('true'); })*/ Student.findClazzNameByStudentId('542b600a683d59a80d4ee632', function (err, student){ if(err) console.log(err); console.log(student.clazzID.clazzName); }) ``` 之前添加了两班级:软件一班和软件二班 我们在新增hacke2时将classID设为软件2班的,查新hacke2时自动就会把关键的 Class查询到 ```js { _id: 542b600a683d59a80d4ee632, name: 'hacke2', clazzID: { _id: 542b5fcc49df6e741d8d15f5, clazzName: '软件2班', __v: 0 }, __v: 0 } ``` end from {{ site.url }} ================================================ FILE: _posts/2014-10-11-the-golden-key.md ================================================ --- layout: post title: 金钥匙 description: "记这两周的起起伏伏" tags: [心情] image: background: witewall_3.png comments: true share: true --- 这两周的心情真是起起伏伏,本来意向是在杭州,结果被分在北京高德,经过无数个人帮我协调,换了好几个部门,最后在神马搜索。 之前根本没有遇到这个情况,以为拿到阿里offer就等于拿到了金钥匙,以后就等着那着这个钥匙去开各个宝箱,职业上专注前端,专注Node.js,带领团队,升职加薪。以后的生活肯定很美满。 这几天的事情让我细细想了一下,这种想法是错的。妄想着一劳永逸的解决自己的职业规划。 人生不是这样,不是拿到一个好offer就能吃一辈子,不期而遇的突发事件才是生活的精彩之处。拿到了offer,生活还得继续,自己还得努力。 生活不是按照你的期望走下去,未来发生的事情不可控,之前自己的善良让自己吃了亏,没有其他offer,没有一个谈判的资本,让自己陷入尴尬的境地。现在明白了,在这个弱肉强食的社会,自己能做的,就是速度快一点,果断反应。 谋事在人,成事在天。感谢生命中那些精彩,让我知足也看的开。自己已经为部门这件事做了努力,剩下的就是靠时间来说明自己的选择是否正确。选择了一个新兴团队,希望能和团队在互联网行业突破重围,克服困难,而不是心神不宁,一直在担心未来会怎样。不走在路上,一直在观望徘徊哪知道这条路是对是错呢? good luck end from{{ site.url }} ================================================ FILE: _posts/2014-10-14-links-share-2014-10-14.md ================================================ --- layout: post title: 前端资源分享 description: "分享一些前端、知乎、行业文章、有些工具资源等" tags: [前端资源分享] image: background: witewall_3.png comments: true share: true --- >跬步积千里,滴水汇长河,整理的一些优秀链接,希望对大家有所帮助 ## 2014年9月 精彩博客集合 https://github.com/hacke2/hacke2.github.io/issues/2 ## 2014年9月 前端资源分享 https://github.com/hacke2/hacke2.github.io/issues/1 ## 2014年10月 上 前端资源分享 https://github.com/hacke2/hacke2.github.io/issues/3 end from {{ site.url }} ================================================ FILE: _posts/2014-10-2-great-nodejs.md ================================================ --- layout: post title: 了不起的Node.js读书笔记 description: "一般结构和思路特别清晰的Node.js入门书籍" tags: [读书笔记, Node.js] image: background: witewall_3.png comments: true share: true ---
home
了不起的Node.js
# 第二章 Js概览 ## 基于GoogleV8引擎 * Object.keys(o) * 数组方法:遍历forEach、过滤filter、改变map * 实现了String.prototype.trim() * 含有JSON解析 # 第三章 阻塞与非阻塞IO ## 单线程注意点 * 小心处理内存中的变量,可能会影响两次访问的结果 * 不要编写阻塞式代码,可能会影响第二次访问的时间 ## 事件轮训 * 先注册事件 * 不断询问这些事件是否已经分发dispatch * 当事件分发了,相应的回调就会被触发 * 如果事件未触发,则继续执行其他代码 * 捕获未来才会执行到的函数所抛出的异常是不可能的 * 避免同步IO # 第四章 Nodez中的Javascript ## global对象 * 对应于浏览器window ## process对象 * process.nextTick == serTimeout(fn,1) ## 模块 * require module exports * NPM * 绝对模块指Node内置模块以及在node_modules模块require(‘fs’) * 相对模块指通过相对路径找的模块require(‘./module’) * 暴漏数据exports.a(多个)、重写module.exports(一个) ##事件 * EventEmitter * 事件监听 on,事件分发emit,事件移除removeListener * 只执行一次once * data事件 数据是部分到达,而不是一次性全到达返回给你 ## buffer * 处理二进制数据 # 第五章CLI &FS API ## Stream * stdin 可读流 stdout/stderr可写流 * stdin.resume 等待用户输入 * console == process.stdout.write(str + ‘\n’) ## fs * readdirSync(__dirname) readFileSync同步 * readdir(‘.’, async) readFile 异步 * createReadSteam 读取可变大小 有data、end事件多用于图片、大型文件等 * watchFile监视文件是否改变 ## process * process.argv 运行的参数 * process.cwd 获取当前工作目录 * process.env 环境变量 * process.exit退出 * 信号量 # 第六章TCP ## TCP * 面向连接,基于IP协议 * IP协议发包无序,TCP给发送的IP包含了标示符和数据流顺序信息 * 面向字节 对字符以及字符编码完全无知,很好的灵活性 * 可靠性 三次握手、窗口时间重发 * 流控制,对接收和发送的速度控制 * 拥堵控制 避免拥堵、数包报的延迟率和丢包率不会太高 ## Telnet * 当不是telnet协议是自动降级为TCP * 发送GET /HTTP/1.1模拟浏览器请求,两次回车 ## net * Node.js核心TCP模块 * data close end事件 * conn.setEncoding(‘utf-8’) 或 Buffer.toString(‘utf-8’) * 服务端createServer 客户端connect方法 connect事件 # 第七章HTTP ## HTTP * TCP上层协议 * Content-Type类型 文本、HTML、XML、JSON、PNG * Transfer-Encoding: chunked 输出的内容长度不能确定,Node天生的异步机制, 这样相应可以逐形成 * 301 永久转移 302 临时转移 304资源未改变 403未授权 404资源未找到 * 表单Content-Type application/x-www-form-unencoded ## http 模块 * request、response 参数 * 判断路由为method+url * querystring模块 将字符串解析成对象 * request方法模拟一个请求,传回来的数据2进制,设置utf-8 * superagent 模拟客户端(模拟一个请求)模块 * up 重启服务器模块 # 第八章Connect ## 工具集&中间件 * 模块:connect * 中间件调用server.user(...) * 日志中间件console.log(‘%s %s ’, req.method, req.url) * 模拟请求时间过长中间件,在res.end是清除定时器 * static中间件 处理静态文件connect.static(‘..’) * query中间件 获取查询字符串connect.query * logger 中间件connect.logger(‘dev’) * body parser中间件connect.bodyParser()如果在POST使用了JSON格式,body parser自动转JSON对象,还可以处理用户上传文件req.body.file多文件表单name 使用name=”file[]” * seesion中间件 会话connect.session 使用方法req.session.user * redis session 会话持久化中间件 * methodOverride 中间件 让低版本浏览器支持PUT、DELETE、PATCH * basicAuth中间件 用户身份验证 # 第九章EXPRESS ## express * WEB应用开发框架,基于Connect * app.set(‘view engine’, ‘ejs’) 设置EJS模板引擎 * app.set(‘view’, __dirname + ‘/view’) 设置视图层路径 * app.set(‘view cache’, ‘true) 设置模板缓存 * 使用app.get()、app.put()、app.post()配置路由,可传参数如:id,获取req.params.id * res.render(‘search’,obj) 渲染模板 * 其他设置大小写敏感、严格路由、jsonp回调 * 为res和req提供了快捷方法 render渲染、redirect重定向req.header检查头信息 * 中间件 app.user(function(req, res, next){}) * 代码组织 分层、分包、MVC,建议仿照Spring MVC ## 模板引擎 Express/Haml/Jade/CoffeeKup/JQuery Templates for node #第十章WebSocket ## Ajax * 异步javascripy * 缺陷;每次都建立HTTP请求,消耗网络资源,不适于实时服务 HTML5 WebSocket * 基于ws协议 * node支持:websocket.io * 客户端new WebSocket ## 缺陷 * 关闭不意味着断开,使用心跳检测机制判断 * 对JSON支持不够友好 * 客户端临时断开无法解决 使用定时器或者刷新页面? * 兼容性 # 第十一章Socket.io ## 优势 * 跨浏览器,支持WebSocket则原生,不支持使用长连接方式,连接会持续打开 20-50s * 即使浏览器支持的WebSocket被代理或者防火墙禁止了,Socket.io仍然会通过其 他技术来解决 * 如果客户端停止传输信息,并且一定时间没有正常的关闭,则认为连接已经断开 * 当连接丢失时,自动重连 * 对逻辑进行分层,有命名空间 * 支持emit分发和listen监听事件 # 第十二章MongoDB ## MongoDB * 面向文档,绝大多数情况是JSON * 连接 new mongodb.Server(‘127.0.0.1’, 27017) * API open insert find ensureIndex findOne * $set 设置 $push 推入数组 ## Mongoose * 类似于JAVA中ORM框架,简化数据库开发 * 连接mongoose.connect(‘mongodb://localhost/database’) * Schema:一种以文件形式存储的数据库模型骨架,不具备数据库的操作能力 * Model: 由Schema发布生成的模型,具有抽象属性和行为的数据库操作对 * Entity:由Model创建的实体,他的操作也会影响数据库 * index 索引 title : {type : String, index : true} unique : true 唯一 * new Schema({}).pre(‘save’, function(){})保存之前做一些处理 * 常用API find,findOne,remove,update,count * new Schema({}).find({_id:‘xx’}).where(‘title’,‘xxx’).sort(‘content’, -1).limit(5).run(function(err, data){}) 类似JQ查询 * .select(‘_id’, ‘name’)选择查询指定字段 * .skip 跳过 mongoose.Schema.ObjectId 生成随机ID * clazz : [Clazz] 嵌套的Sehema * clazz: {type : ObjectId , ref: ‘Clazz’} 关联查询 * 添加静态PersonSchema.statics.findByName通过model调用 * 添加实例方法 PersonSchema.methods.findSimilarTypes Entity调用 # 第十三章MySQL ## MySQL * 主要使用query执行SQL * 有占位符 ? 插入数据(和JAVA一样) * 使用seqelize Node版ORM框架 # 第十四章Redis ## Redis * 应用场景:简单数据模型、数据集。适合存储在内存 * seesion持久化 # 第十五章 代码共享 ## 书写兼容性代码 * 导出模块 ,前端后端都可使用 (function(module){ module.exports = function(a, b ){ return a+b; } If(‘undefined’ != typeof window) { Window.add = module.exports; } })(‘undefined’ == typeof module ? {module : {exports : {}}}: module) * 模拟实现ECMA:扩展原型或者实现工具函数 * EventEmitter。Assert、XMLHttpRequest、DOM、WebSocket、node-canvas都在 git上有实现 * bowserbuild 将node模块转为浏览器可识别的代码 # 第十六章测试 ## assert * 常用API ok,be/equal/eql/a/macth... ## Mocha * 简化书写 * 测试异步代码 * 生成报告 end from {{ site.url }} ================================================ FILE: _posts/2014-10-20-tink-in-Ghostjs.md ================================================ --- layout: post title: GhostJs源码目录 description: "GhostJs源码的学习" tags: [JavaScript] image: background: witewall_3.png comments: true share: true --- 之前[Demon](http://Demon.com)写了一个JS类库[Ghosejs](https://github.com/DemonCloud/GhostJS),最近我打算学一下。 先贴一下整理的整体结构,前面的为行数。 ```js /** 名称 Ghostjs 版本 0.0.3 21 入口 38 G()函数入口,DOM加载后执行,兼容FF和IE,类似于$(function(){});如果是结点会走_G() 116 引用了一些常见的方法 146 常见的正则 186 全局缓存 204 UA判断 220 _G() 选择器包装入口,有length等,具体选择器在selector数组里 276 queueSelector复杂选择器 G('div>p +warp'),最终找到P 353 选择器入口 支持ID CLASS TAG * >选择 378 _G原型方法 381 each 对遍历元素的迭代,参数返回为dom, index, this(当前遍历对象) 441 signet 给_G元素加属性 455 at 类似于JQ的eq 477 back G(elm).at(n).xxx(做一堆事情).back().yyy(在原来的选择器上做事情) 510 first last 525 fix 调用AryFilter 541 even odd 563 next prev siblings 667 warp 所选元素的子元素 warpClass warpTag ctains 724 obstruct 阻塞执行 741 trash 清楚缓存 765 事件 bind unbind once 811 CSS方法 hide show adClass rmClass tgClass stStyle gtStyle 909 属性方法stAttr rmAttr gtAttr 948 动画 animate fdIn fdOut 1027 DOM操作 insetHTML gtInHTML insetText gtInText rmNode apend 1108 位置与大小 w h gtPos scTo bscTop bscBtom 1167 G的共用方法 实现了一些本类库的内部方法,兼容了部分ECMAScript 5、ECMAScript 6方法 1732 编码的一些转换 1801 模板引擎 1875 一些判断 返回bool类型那种 1921 ajax 1968 jsonp 2036 cookie 2141 事件 2256 其他内部方法 2411 Tween动画算法 **/ ``` 大致的结构如此,今天和作者聊了一下午,他打算吧动画那一块还要做一些事情,加入点高级动画的特效 里面有很多技巧,比如项目入口就是兼容非CMD模块加载的解决方案,作者说还没加上去 还有各种部分ECMAScript 5、ECMAScript 6兼容方法 ```js G.AryFilter = function(ary, func) { if (nativeFilter) { //ECMAScript 5 filter // func(e) 返回为 true. 则会被保留. 如果 func(e) 返回为false. 那就会被过滤从数组中移除掉 G.AryFilter = function(ary, func) { return ary.filter(func) }; } else { G.AryFilter = function(ary, func) { //ary [1,2,3,4,5] //func function(e){ return e!==4 } -> [1,2,3,5] for(var i = ary.length;i--;){ if (!func(ary[i],i)) splice.call(ary,i,1); } return ary; }; } return G.AryFilter(ary, func) }; ``` 还有一些函数的处理技巧,如下时间绑定,在第一次判断兼容性,将函数覆盖,之后就不用判断了 ```js function OneBind(e, event, callback) { if (doc.addEventListener) { OneBind = function(el, eve, call) { el.addEventListener(eve, function() { el.removeEventListener(eve, arguments.callee); call.call(el); }); }; } else { OneBind = function(el, eve, call) { el.attachEvent("on" + eve, function() { call.call(el); el.detachEvent("on" + eve, arguments.callee); }); }; } return OneBind(e, event, callback); } ``` DEMON一个乐于分享自己的一些经验的同学,知识只从外国网站获取,是一位很有潜力的牛人,以后混的好可别忘了我啊:D **end from [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2014-10-22-html5-javascript-web-dep.md ================================================ --- layout: post title: HTML5和JavaScript Web应用开发读书笔记 description: "《HTML5和JavaScript Web应用开发》主要介绍如何使用HTML5、JavaScript和最新的W3C规范构建可用于所有浏览器和设备的Web应用" tags: [读书笔记] image: background: witewall_3.png comments: true share: true ---
home
HTML5和JavaScript Web应用开发
# 第一章 客户端架构 * 过去前端开发人员不用关心用户界面后的框架,从未先现在一样关注浏览器的性能 * 过去利用服务端模板和组件框架,如JAVA 的JSP,Velocity,前端只是套个模板而已 * 如今浏览器的性能已经得到了很大的提升,很多逻辑在客户端写,而后台仅仅提供Restful风格的接口 * 我们现在创建的不是网站,而是基于HTML5,CSS3和JavaScript的**可靠地应用程序** * 服务端模板引擎已经逐渐被JavaScript模板所取代 * JavaScript API得到了更多硬件访问的支持,例如Geolocation、Web Workers * 应该为当前的项目作出价值的决策,必须建立和维护编写、测试、和调试代码与选择框架的工作流 * [WEB开发模式已经逐渐在演化](https://github.com/lifesinger/lifesinger.github.com/issues/184),我们前端是新时代的先锋,抓住这个机会,创建性能强大,可伸缩的应用,利用WEB最新规范将其推进一步,变得更好。 # 第二章 移动WEB * 28%会使用移动设备来冲浪,但也有很大一部分人使用IE9以下的浏览器 * 维基百科 [HTML5引擎对比页面](http://en.wikipedia.org/wiki/Comparison-of-layout_engines_(HTML5)) * Webkit、Mobile Firefox、OperaMobile * [浏览器的市场份额](http://www.netmarketshare.com/browser-market-share.aspx?qprid=0&qpcustomd=1),[最新浏览器HTML5支持](http://caniuse.com) * 优雅降级 * [移动设备模拟器列表](http://www.mobilexweb.com/emulators) # 第三章 为移动WEB构造程序 * 移动设备的成功依赖于两个因素:所以平台一致的外观;具有离线能力、UI动画和通过Rest风格或者WebSocket端点读取和发送数据的后端服务 * 有两个影响:CPU/GPU的速度和互联网速度 * [移动端的设计模式](http://mobiledesignpatterngallery.com/mobile-patterns.php) * 要考虑的事情:硬件加速,内存分配和计算负担,电池的消耗与寿命,使用canvas代替image * [常见交互](http://html5e.org/example) 滑动,翻转,旋转transtion,transtform,transtlate.建议使用translate3d, 2d转换不支持GPU加速 * 使用Chrome查看每秒帧数FPS,查看是否加速用合成渲染边框 * [读取和缓冲,将AJAX缓冲到localStorage](https://github.com/html5e/slidfast/blob/master/slidfast.js#264) 可存5M * IOS使用InnerHTML可能会出问题 * [网络类型检测与处理](https://github.com/html5e/slidfast/blob/master/slidfast.js#L536) * 移动WEB常用于单页,可以平滑的向原生应用过渡,减少了请求次数 ## 移动框架 * 对触摸屏设备的优化,确保使用CSS3过渡处理动画 * 在所有主流平台浏览器上跨平台一致性 * 使用或封装最新的HTML5 CSS3标准 * 框架背后的强大开源社区 * 单页框架:[JQuery Mobile](http://jquerymobile.com), [JQTouch](http://jqtouch.com) * 无页面结构框架:[xui](http://xuijs.com) * 100%JavaScript驱动:[SenchaTouch](http://www.sencha.com/products/touch), [Wink Tooolkit](http://www.sencha.com/products/touch), [The-M-Project](http://the-m-project.net) ## 移动调试 * [weinre](http://people.apache.org/~pmuellr/weinre) * [Adobe Shadow](http://labs.adobe.com/technologies/shadow) * [Opera远程调试](http://www.opera.com/dragonfly/documentation/remote) # 第四章 桌面WEB * 由于AJAX的出现,在后台生成HTML的时代宣告结束 * 在客户端生成HTML降低了服务器的负载 * [使用HTML5 + Node.js的例子,比前身快2~10倍](http://venturebeat.com/2011/08/16/linkedin-node) ### 客户端优势 * 更好的用户体验 * 网络带宽减少(降低成本) * 具有可移植性(离线) ### 服务端优势 * 更好的安全性 * 减少客户端的处理开销(移动端电池) * 具有可扩展性(方便添加更多服务器) * 性能检测可以使用UA或[Modernizr.js](http://modernizr.com) ## 特征检测 * 原生特征检测一般使用创建一个元素看某一个方法是否存在 * 使用[Modernizr.js](http://modernizr.com)可能会带来加载时间超过30MS,因为必须在DOM加载之前渲染各个值, * 使用[Modernizr.js](http://modernizr.com)不建议在生产环境中使用,但是可以在开发阶段使用它将各个浏览器的兼容性先调试好 * [FormFactory.js](https://github.com/PaulKinlan/formfactor)可以检测不同类型的的设备,如移动设备版本、电视版本 ## UA * window.navigator.userAgent检测,但是不可靠 * Google的一款UA解析器,基于JSON[ua-parser](https://github.com/tobie/ua-parser),另一款基于JS[platform.js](https://github.com/bestiejs/platform.js) * 服务端检测,[MobileESP](http://blog.mobileesp.com)用来检测userAgent的首标 ## 压缩 * 确保之压缩可压缩的内容,不要把资源浪费在可压可不压的内容上 * 未访问这选择正确的压缩方案 * 正确配置WEB服务器,将压缩的内容发给具体的有解压能力的客户端 * 如果一个大型页面(20k ~ 30K)压缩可能会加载CPU的负载,远大于SQL的执行,建议不压身 * 压缩的目标可以有HTML, CSS, JS, XML, JSON, HTC, TXT(Robots.txt) * 可使用GZIP(减少70%,90%浏览器支持)或DEFLATE * 压缩JS和CSS的工具有Closure Cpmpiler, Yahoo!YUI Compressor, JSMin, Packer, * 前端构建(验证压缩合并等)[grunt](https://github.com/gruntjs/grunt), [Jawr](http://jawr.java.net), [Ziproxy](http://ziproxy.sourceforge.net) ## JavaScript MVC框架 * [常见MV*框架的演示程序 TodoMVC](https://github.com/tastejs/todomvc) * [Backbone](https://github.com/jashkenas/backbone) [书中的例子](https://github.com/html5e/backbone-jax-cellar) * [Ember](https://github.com/emberjs/ember.js) [书中的例子](https://github.com/html5e/ember_data_example) * [Angular](https://github.com/angular/angular.js) [AngularJS译本](https://github.com/peiransun/angularjs-cn) [书中的例子](https://github.com/html5e/angular-phonecat-mongodb-rest) * [Batman](https://github.com/batmanjs/batman) [书中的例子](https://github.com/html5e/batmanjs-address-book) * [Knockout](https://github.com/knockout/knockout) [书中的例子](https://github.com/html5e/knockout-rest) # 第五章 WebSocket * 从浏览器发出请求包含了header,无压缩的header可能有200b~2kb之前 * WebSocket通过套接字的全双工同学,是客户端和服务器通信的高效手段 * 优雅降级是指在WebSocket不受支持时退回到就技术(Flash或长轮训) * NIO和线程直接的争论永无止境,一般的,高并发建议NIO,对计算有很大要求的用多线程 * 观察者模式,一般的有三个事件,OPEN, MESSSAGE, CLOSE * [使用Jetty服务器实现WebSocket的例子](https://github.com/html5e/HTML5-Mobile-WebSocket) * 不仅可以传递文本,JSON,而且可以传递二进制,用老发送流式音频,也可以提供画布(你画我猜)与实时的屏幕共享技术 * 使用代理技术如HAProxy让拆除服务器又不影响新的服务 ### 优势 * 没有HTTP Header * 没有持续(Keep-alive)问题引起的时滞 * 低延时,更好地吞吐量和相应能力 * 对移动设备的电池有利 ## 框架 * [Ver.x](https://github.com/vert-x/vert.x)是一个全异步,通用的JVM语言应用容器,是JVM版的Node.js * [Socket.io](http://socket.io)目标是在每种浏览器和移动设备实现应用,优雅降级 * [Atmosphere](https://github.com/Atmosphere)可运行在任何基于JAVA的WEB框架 # 第六章 Web Storage * Cookie只有4K的存储量,而且每次都会带在HTTP请求头,与服务器共享 * Web Storage有5M,但是存储数据若为UTF-16只有2.6M,IE和Firefox除外 * Web Storage分为localStorage和sessionStorage(关闭浏览器或标签会消失) * 读取或存储数据,是阻塞式的 * 常用API:length;key(n);getItem(key);setItem(key, value);removeItem(key);clear(); * [使用localStorage[name] = 'hacke2',localStorage.name = 'hacke2', localStorage.setItem('name', 'hacke2')效率不同](http://jsperf.com/localstorage-key-vs-localstorage-key-vs-localstorage-ge) * 同步问题使用StorageEventAPI解决 * 使用JSON在Web Storage上要进行编码和解码 * 无安全性可言 * 浏览器隐私模式下存储会出现异常 * 使用场景,存储一些Base64的图片,用户搜索的一些数据(神马搜索)等,QQ空间、Disqus评论将草稿存储在loaclStorage,自动登录,[带有时间戳的缓存](https://github.com/pamelafox/lscache)等 * [从客户端缓冲数据 允许离线且连接到网络时刷新数据](http://engineering.linkedin.com/mobile/linkedin-ipad-using-local-storage-snappy-mobile-apps) * [用Backbone进行数据库同步](http://blog.oxfordcc.co.uk/backbone-local-storage-and-server-synchronization) * [在任何浏览器中使用Web Storage](https://github.com/wojodesign/local-storage-js) ## 框架 * [LawnChair](https://github.com/brianleroux/lawnchair)轻量级支持移动设备 * [persistence.js](https://github.com/coresmart/persistencejs)用于服务端,集成Node.js和MySQL # 第七章 Geolocation * 提供对于宿主设备相关的地理位置信息的脚本访问,定位用户移动时跟踪器经纬度 * 地理防护:进入或离开一个位置进行提醒 * 地址匹配:利用Google将经纬度转为实际地址 * 一般跟踪:跟踪汽车,走路,跑步的距离 * navigator.geolocation.getCurrentPosition(function(){}) * [防止激活Geolocation的一个变通方法](http://html5e.org/example/geo) * 实例:用户跟踪,反向地址匹配 * [Geolocation API跨浏览器支持](http://bit.ly/Geolocation-API-Polyfill) ## 框架 * [geo-loaction-javascript](code.google.com/p/geo-location-javascript) * [Webshims lib](https://github.com/aFarkas/webshim) # 第八章 Device Orientation API * 加速度计,陀螺仪,指南针 * [实例:用设备的移动完成滚动](http://www.html5e.org/example/orientation) # 第九章 Web Workers * 当WEB应用需要在JavaScript进行繁重的工作和后台处理的时候,推荐使用 * [Web Worker API检测](http://html5-shims.googlecode.com/svn/trunk/demo/workers.html) * [Web Worker 性能测试](http://html5-demos.appspot.com/staic/workers/transferables/index.html) * [实例:池化和并行作业](http://html5e.org/example/workers) [对应的演示程序](https://github.com/html5e/slidfast/blob/master/example/workers/index.html) * [实例:处理图像](http://www.smartjava.org/examples/webworkers2) **end from [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2014-10-29-2014-10-d2.md ================================================ --- layout: post title: 2014.10.25 杭州D2行 description: "2014.10.25 杭州D2行" tags: [游记] image: background: witewall_3.png comments: true share: true ---
home
D2前端技术论坛
>参加交流会坐在台下看台上大牛的分享,有种时光交错的感觉。在一个最好的时代与时间最精英的人,挤挤一堂,梦想成为这个时代最有创造力的一代,而不是垮掉的一代。----豪情 这次D2和[@liningone](https://github.com/liningone)一起去的,25个小时的火车(没钱啊TT),一路奔波,终于来到了杭州,短暂休息了一下,就跑去阿里西溪总部先观望了一下,结果门卫大哥不让我们进去,只好在门口照了几张相
home
阿里巴巴西溪园区门口
回去收拾了一下就睡了,整整一天的火车还是挺累的。 第二天一早,就打的又过去,这次有阿里的同学负责接应,最后到了会场,扫描了二维码,然后碰见以为百度地图的小伙在那边聊天,期间碰见了[@小胡子哥](https://github.com/barretlee),打了个招呼,也合了个影:)。
home
和小胡子哥
后来还看见[@张鑫旭](http://www.zhangxinxu.com/wordpress/),旁边围的人挺多的,如众星捧月一般,没好意思过去,只是远远的观望了一下,很黑很高很瘦,不知道的还以为田径运动员。没多久,就和[@liningone](https://github.com/liningone)一起进入到会场。
home
D2大会开始前
很兴奋,主持人[@aoao](http://weibo.com/aoaoing?topnav=1&wvr=6&topsug=1)一口标准的普通话让人捧腹大笑,期间和旁边的一位杭州就职的聊了几句,我称呼您,他说他不适应。。会议开始了,首先将D2的创始人圆心简单的介绍了一下D2的创建以及历史,和对中国前端的一些展望,然后正式开始,因为很想听听[《支付宝前后端分离的思考与实践》](http://vdisk.weibo.com/s/C30SUspJtfe1v),所以果断去了分会场,没想到一进去,这么多人。。没地方做,站着听了一会。苏千引入了一个“大前端”的概念,主要讲了为什么进行前后端的分离,谈了谈更清晰的职责划分的优势,并引出了阿里chair框架,对模板引擎,单元测试,自动化UI测试,性能提升,日志报警等都有很好的解决方案。听完后就去吃饭,排了很长的队伍,但是阿里的饭菜还是很给力的
home
阿里食堂的伙食
吃完饭和[@liningone](https://github.com/liningone)去器自行车逛了一圈阿里西溪园区,后来接到[豪哥](https://github.com/jikeytang)电话,终于看见心中的男神了,聊了一会儿,大家过去D2门口照了张相
home
从左往右:豪情,ngot, 桃子, undefined, 我
和群友一起进入主会场,听接下来的三场:[nodejs一小步 前端开发一大步](http://vdisk.weibo.com/s/C30SUspJtfe4O),[第三方开发前端实践](http://vdisk.weibo.com/s/C30SUspJtfdhI),[面向多端的蘑菇街前端技术架构](http://vdisk.weibo.com/s/C30SUspJtfdi5),[京东前端工业化实践之路](http://vdisk.weibo.com/s/C30SUspJtf4sv),由于下午精神不是很好,听的不是很仔细,去和豪哥上厕所是碰见大神[winter](http://weibo.com/wintercn?from=feed&loc=nickname),赶紧去握了一下手:D(其实是去外面休息一下碰见了) 总结一下,下午主要听了有两个概念:Node.js和前端工业化。由于Node.js的出现,让我们前端成为全栈成为可能,我们可以发挥自己想象力为项目或者产品做更多的事情,同时,前端也有了更多的职责,更大的压力,地位也是显著的提高。对于前端工业化,我之前有一个文章没写完[高大上Web前端开发环境](http://www.hacke2.cn/web-development-process/),其实就是各个公司讲了一下他们公司对于前端工业化的思路与实现,下来再看看分享的PPT。 第二天和liningone去西湖玩,租了自行车沿着西湖转了一圈,整整一天!沿途的风光很美,特别是在雷峰塔上的广阔视野和断桥残雪上的夕阳。下次来的话一定得去一下钱塘江感受一下!
home
雷峰塔
home
断桥残雪
**end from [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2014-10-3-node-westom-mina.md ================================================ --- layout: post title: 利用Node.js对某智能家居服务器重构 description: "利用Node.js天生NIO特性" tags: [Mongoose] image: background: witewall_3.png comments: true share: true --- 之前负责过一个智能家居项目的开发,外包重庆一家公司的,我们主要开发服务器监控和集群版管理。 移动端和机顶盒的远程通信是用中间服务器完成交互,服务器使用MINA NIO框架,非阻塞式的,可以看看以前博客了解下某智能家居项目框架学习总结,或者其他资料JAVA NIO原理基于MINA框架快速开发网络应用程序。 在移动端或者机顶盒登录后会使用spring security 进行加密,主要是结合用户名和密码来加密,生成一个唯一标示符。服务器来到一个请求时会检查对应的标示符来发送相关约定好的命令,如登录到移动端向服务器发送命名,服务器会生成如522f9e2a459de81d6a9e9eadfa9468d1的标示符,如果在机顶盒集合里也存在相应标示符的主控,则给他发送。 最近关注Node.js,这不就是Node的特性NIO吗? 让我们来着手重构一下,利用Node.js的先天优势,高并发,非阻塞式 ```js var MyClient = function (client, username, password, type){ this.client = client; this.username = username; this.password = password; this.type = type; //0是机顶盒,1是客户端 } MyClient.prototype.write = function(msg) { this.client.write(msg + '\r\n'); } module.exports = MyClient; ``` 每一个连接都有它的用户名和密码,也有它的client,也就是Socket。也有一个标示符,表示是主控还是客户端 然后添加一个原型方法,用来向当前client发送信息 下面就是编写主程序了,使用Node.js进行网络应用程序的开发很简单,详细大家能看懂 ```js //tcp var net = require('net'); var crypto = require('crypto'); var MyClient = require('./MyClient'); var server = net.createServer(); //客户端,如平台、移动端进来放在这个数组中 var clientArr = []; //主控端,主要装的是机顶盒的连接 var boxArr = []; server.on('connection', function(client){ client.setEncoding('utf-8'); client.write('plase input name|password|type :\n'); var myClient; var message = ''; //发送消息 client.on('data', function(data){ //如果是非回车则累加 if('\r\n' != data || data == '' || data == null) { message += data; }else { //说明是已经注册的client if(myClient) { sendMsg(message, myClient); }else{//说明是第一次进来 var userInfo = message.split('|'); var md5 = crypto.createHash('md5'); //使用用户名和密码进行加密,放入password中 md5.update(userInfo[0] + userInfo[1]); var password = md5.digest('hex'); myClient = new MyClient(client, userInfo[0], password, +userInfo[2]); //如果是客户端 if(myClient.type) { clientArr.push(myClient); }else { boxArr.push(myClient); } console.log('新加用户' + password); } message = ''; } }) //断开时移除这个客户端 client.on('end', function(data){ console.log('end....'); //有还未登录就退出的情况 if(myClient) { if(myClient.type) { clientArr.splice(clientArr.indexOf(myClient), 1) }else { boxArr.splice(boxArr.indexOf(myClient), 1) } } }) }) server.listen(3000); function sendMsg(msg, myClient) { console.log(' sendMsg : ' + msg); var array = myClient.type == 1 ? boxArr : clientArr; for (var i = 0; i < array.length; i++) { if (myClient.password == array[i].password) { array[i].write(msg); console.log(myClient.name + myClient.type == 1 ? '移动端' : '主控' + '发送消息....'); }; }; } console.log('listening....'); ``` 我们来测试一下,利用telnet,使用约定好的协议进行登录,cqut 123456 1,cqut 123456 0,cqut2 123456 1,(这里不是空格,而是I符号,在文章内显示有问题,具体看代码分割就明白了)可以看到,cqut只是给cqut的机顶盒发送,而cqut2的接受不到。
home
只给对应的设备发,给其他设备不会发送
当然Mina还有其强大的过滤器,利用Node.js的中间件就能很好的实现,请读者自行研究 end from {{ site.url }} ================================================ FILE: _posts/2014-10-31-es6-modules-today-with-6to5.md ================================================ --- layout: post title: 译-使用6to5,让今天就来写ES6的模块化开发! description: "ES6 modules today with 6to5" tags: [翻译] image: background: witewall_3.png comments: true share: true --- >http://es6rocks.com/2014/10/es6-modules-today-with-6to5/?utm_source=javascriptweekly&utm_medium=email 原文链接 我之前在Twitter上发过一个照片,表达出我有多快乐,这像是一个时光机让我们可以穿梭到未来-ES6的时代!下面让我来展示如何使用6to5让今天就可以练手ES6的模块化。
home
使用6to5让今天就可以练手ES6的模块化
# 第一步 如果你现在还不了解ES6的模块化开发,请在[JSModules.io](http://JSModules.io)上了解一下。我也推荐大家读一下@jcemer的文章[A new syntax for modules in ES6](http://es6rocks.com/2014/07/a-new-syntax-for-modules-in-es6/),它涉及到了很多非常酷的关于JS模块化的东西。他可以指导我们使用6to5。总的来说,6to5能在支持ES5d的环境下提前尝试ES6 模块化开发的快感。 6to5比其他降级工具有一下几个优势: * 可读性:你的格式化的代码尽可能的保留。 * 可扩展性:有非常庞大的插件库和浏览器的支持。 * 可调式性:因为支持source map,你可以方便的调试已经编译过后的代码 * 高效率:直接转化为与ES相当的代码,不会增加额外的运行十几 # 一起来写模块 让我们来尝试着写模块吧! 我们的应用除了输出日志不会做其他事情,其主要的目的就是让你了解模块化如何工作和如何让你现有的环境使用ES6的模块化开发。 基本的目录结构: ├── Gruntfile.js ├── package.json └── src ├── app.js ├── modules │ ├── bar.js │ ├── baz.js │ └── foo.js └── sample └── index.html app.js是主程序,包含了我们将要存储的模块化的目录 下面是app.js的代码: ```js import foo from "./modules/foo"; import bar from "./modules/bar"; console.log('From module foo >>> ', foo); console.log('From module bar >>> ', bar); ``` 以上代码非常简单,我们导入了foo模块和bar模块,然后分别打印出他们 ```js // foo.js let foo = 'foo'; export default foo; // bar.js let bar = 'bar'; export default bar; ``` 在这些模块一面我们只是导出了两个字符串'foo'和'bar',当我们导入这些模块,我们的变量其实已经有数据。 当然,我们何以导出对象,类,函数,等等 现在,你可以开始模仿这个例子写出你自己的模块 # 构建 就像你已经知道的,[ES6不支持你现在的浏览器和Node](http://kangax.github.io/compat-table/es6/).js,只有一条路,那就是使用降级转换器来编写ES6的模块化代码。 正如我之前提到的那个,我使用6to5,他可以精确的达到我们想要的效果。 这个任务是运行在Grunt上的,我们使用 @sindresorhus的 [grunt-6to5](https://github.com/sindresorhus/grunt-6to5) ```js npm install grunt-cli -g npm install grunt --save-dev npm install grunt-6to5 --save-dev ``` 我们的Gruntfile类似于一下: ```js grunt.initConfig({ '6to5': { options: { modules: 'common' }, build: { files: [{ expand: true, cwd: 'src/', src: ['**/*.js'], dest: 'dist/', }], } } }); ``` To test it in the browser, I made a copy task that just copies the sample/index.html file to our dist directory. The HTML file looks like this: 这是个简单又给力的配置,我们也几乎完成了。 当你指定好源文件和输出文件后,这个任务就可以来运行了。 'common'选项的目的在于告诉6to5我们将输出ES5CommonJS模块化风格。 当然,6to5也支持AMD,我写了sample/index.html,让他在浏览器环境下测试一下,这个HTML的代码如下: ```html ES6 modules 6to5 ``` 观察上面的代码,我们使用AMD的RequireJS框架来加载这个JS,对于这个例子,你需要将上面的配置文件改为 modules: 'amd' # 运行 万事俱备东风只欠,我们的代码已经放在[es6-modules-today-with-6to5](https://github.com/es6rocks/es6-modules-today-with-6to5),你可以克隆下来自己玩玩。使用npm install安装6to5 记住一点,Grunt任务会生成一个目标文件夹来存放转化后的代码 所以,如果你想测试使用CommonJS规范的转化后的ES6的代码,你可以执行一下命令 node dist/app.js
home
Node.js上的运行效果
如果你使用AMD规范的,请在浏览器访问index.html(**吐槽一下,老外竟然不知道中国的[sea.js](https://github.com/seajs/seajs)**)
home
在浏览器执行的效果
# 结论 通过这个简单的实例你学会了如果简单的使用ES6模块化风格来编写代码。6to5是胃肠棒的工具让你穿越到未来提前体验ES6模块化带来的快感。资源下载[es6-modules-today-with-6to5](https://github.com/es6rocks/es6-modules-today-with-6to5),欢迎提交一些问题的反馈 **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2014-11-10-javascript-aop.md ================================================ --- layout: post title: 简单几行代码,实现JavaScript中的AOP description: "JavaScript中的AOP的模拟与实现" tags: [JavaScript] image: background: witewall_3.png comments: true share: true --- >AOP我们在后台常常听见,那么在前端是否也有这个概念呢? ## 现存的方式 我们写了一段计算比较密集的代码: ```js function complexFunc() { sleep(1000) } function sleep(maxtime) { var now = +new Date(); while(true) { if(+new Date() - now > maxtime) { break; } } } ``` 我们在这边定义一个复杂函数,用一个伪睡眠函数模拟。突然需求来了: >产品经理:这个函数计算时间挺多的,我们想将这个函数在客户机器上真实的运行时间记录下来做一些统计,你把这个代码改改,加一个统计运行时间的功能发给后台。 >前端程序员:哦,好的。 现在我们向在这个`complexFunc`函数中做执行时间的记录。动手改一下上面的代码。 ```js function complexFunc() { var now = +new Date(); sleep(1000); //$.get('xxx/log.do?time' + +new Date() - now > maxtime + '&funcName' + arguments.callee.name) 发送给后台 console.log(+new Date() - now, funcName); //模拟运行 } //运行结果: 1014 ``` 代码开开心心写完了,然后需求又来了。 >产品经理:其他几个函数也加一下吧 >前端程序员:嗯,行。 ```js function complexFunc() { var now = +new Date(); sleep(1000); //$.get('xxx/log.do?time' + +new Date() - now > maxtime + '&funcName' + arguments.callee.name) 发送给后台 console.log(+new Date() - now, funcName); //模拟运行 } function otherComplexFunc() { var now = +new Date(); //和上面完全重复,复制过来俗称COPY 改 sleep(1500); //$.get('xxx/log.do?time' + +new Date() - now > maxtime + '&funcName' + arguments.callee.name) 完全重复 console.log(+new Date() - now, funcName); //模拟运行 } //其它要加统计时间代码的函数 //.... ``` 后台也做了统计完成后: >产品经理:嗯,这些函数现在没必要统计他的执行时间,你去把那些你家的代码给去掉。 >前端程序员:What the f2ck? 已经上线的项目不能轻易改动,更何况改动如此之大,找都得找半天苦逼的程序员如果出错就只能怪在你身上了T T ## 什么是AOP? AOP这个概念是来源于后台开发,指面向切面编程。在我们的项目中一般仅限于声明式事务,不过后来在做豌豆荚社区时用到了AOP的异常捕获、会员积分记录、日志系统等。AOP的使用大大的降低了代码的耦合度。真真的实现了代码的可插拔。 可以使用现实中的例子:年轮。树的年轮用来记录树的年龄,每一年加一轮。我们可以把与业务无关的代码就像是年轮一样将之包裹,并没有侵害已有代码,如果不需要这个功能,就在调用的时候给去掉。**要知道,在调用处改比在函数实现内部改要好一万倍!** 传统AOP的实现原理为动态代理,我之前层深入分析过[对Spring.Net的AOP一些思考及应用](http://blog.csdn.net/hacke2/article/details/12753379),[动态代理及JDK动态代理源码分析](http://blog.csdn.net/hacke2/article/details/23712633)。每一个代理类不用重新定义,而是只要你符合那个规范会利用反射技术动态生成出那个代理对象。我们使用JavaScript语言的特殊性,轻轻松松就可以实现代码的可插拔。 ## 函数的封装 我们知道,我们可以给JavaScript原生对象扩展其属性、方法。JavaScript对于`功能的封装`就是在函数里,我们在函数里面扩展一个before方法。 ```js //前置通知 Function.prototype.before = function(func) { var that = this; args = [].slice.call(arguments,1); return function() { //debugger if(func.apply(this, args) === false) { return false; } return that.apply(this, arguments); } } //后置通知 Function.prototype.after = function(func) { var that = this; args = [].slice.call(arguments,1); return function() { var ret = that.apply(this, arguments); if(ret === false) { return false; } func.apply(this, args); return ret; } } //环绕型 Function.prototype.around = function(beforeFunc, afterFunc) { var that = this; return function() { return that.before(beforeFunc).after(afterFunc).apply(this, args); } } //捕获异常 Function.prototype.throwing = function(throwingFunc) { var that = this; args = [].slice.call(arguments,1); return function() { try { return that.apply(this, arguments); } catch(e) { throwingFunc && throwingFunc.call(this, e, args); } } } ``` 这里先只提供四个API: * 前置通知before:在函数调用之前调用的函数func * 后置通知after:在函数调用之后调用的函数func * 环绕通知around:传递前置、后置函数,将其包裹 * 抛出异常后通知throwing:异常的控制 只说说第一个函数,这里有一个闭包,引用了上一层传来`this`和`arugments`的返回一个加工后的函数。在这里我们并不是简简单单的只是将功能函数在业务函数之前执行,而是判断了一下功能函数的返回值,如果是`false`。则不执行已有函数,类似于一个`拦截器`或者`过滤器`的功能,在NOde.js叫中间件。 因为是直接扩展在`Function`上的,可以进行`链式操作`。如: ```js func.before(func1).before(func2).after(func2)(arg1) ``` ## 重构上面的代码 下面,我们来重构一下 ```js //将时间记录函数封装一下 function logTime (func) { return func = (function() { var d; return func.around(function() { d = +new Date(); },function() { console.log(+new Date() - d, func.name); }); })() } //像年轮一样将业务函数包裹,不会污染已有代码 logTime(complexFunc)(); logTime(otherComplexFunc)(); //运行结果: 1014 2024 ```
home
像年轮一样无限扩展。。这就是AOP!
我们的代码就像被年轮函数被包裹,而且此函数可以再次被年轮函数包裹!如果需求改动,只需轻轻松松的改动`调用处而非实现处`是不是瞬间世界变得美好了? ## 一些使用场景 上面的代码只是一个使用场景,下面列举几个常见的场景: * 将一些敏感字符或需要转码的字符过滤,而这个方法并不和业务代码产生耦合。使用`before`几个实现。 * 如本例日志的记录。 * 数据的验证,如果不通过不会执行业务代码,一般为`submit`,并且将逻辑进行了分离。 * 无限的想象力... * 异常的控制 ## 一些展望 在Spring提供的AOP,我们有一个非常强大的功能:`切入点表达式`,比如一下代码: ```js execution(* com.spring.service.*.*(String,..)) and args(msg,..) ``` 我们可以写一个表达式来动态的给函数来绑定一些前置通知,后置通知等。在JavaScript中,我们可以使用正则来完成定义表达式的策略。扫描当前JS的函数后包装函数,要修改功能只需动态的修改配置就可以实现功能的插拔,真正意义上实现JavaScript的AOP! ## 总结 总的来说有一下几点好处: * 降低模块的耦合度 * 使系统容易扩展 * 更好的代码复用性 使用面向切面编程能将我们的代码逻辑进行分离,将问题细化为单独部分,即可以理解为不可再分割的组件,如上边的日志组件,更好地实现模块化、组件化。还不赶紧重构你的代码? **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2014-11-11-hello-mobile-aop.md ================================================ --- layout: post title: 移动Web初级入门 description: "入门移动Web的一些思考" tags: [移动Web] image: background: witewall_3.png comments: true share: true --- >最好的阅读是输出。 --玉伯 即将开始涉入移动Web了,有点小兴奋也有点小紧张,希望能在未来的团队里带来一些价值。记录一下我现在所认识的移动Web。 ## 一些基本名词 初涉移动Web,会有一些基本的名称需要掌握,什么设备像素比呀,移动端Web的内核呀,viewport呀,屏幕的的最小物理单位呀。我已经记录了一些,以后还得继续补充。 - \- [常见移动Web名词](https://www.zybuluo.com/hacke2/note/45079) ## 关于布局 我们来看看移动端最常见的布局:
home
上中下三部分布局
下面实现上述页面常见移动Web布局三种方法: * fixed * absolute * flexbox ### fixed 对于第一种布局,其实现原理就是header和footer部分都为fixed定位。内容页面可以使用`-webkit-overflow-scrolling:touch`来进行滚动,当然,对于不支持的,也可以使用[iscroll](https://github.com/cubiq/iscroll)来兼容。 fixed布局网上人说坑太多,滚动的时候bug太多,特别是表单元素时弹出输入法会有很多问题,所以不建议使用,以下是一些网上参考的资料: - \- [移动Web开发实践——解决position:fixed自适应BUG](https://github.com/maxzhang/maxzhang.github.com/issues/11) - \- [移动端web页面使用position:fixed问题总结](https://github.com/maxzhang/maxzhang.github.com/issues/2) - \- [移动Web开发,4行代码检测浏览器是否支持position:fixed](https://github.com/maxzhang/maxzhang.github.com/issues/7) ### absolute 和fixed一样,只不过将fixed定位设为绝对定位。设定其left,right等值。下面有一个绝对定位的DEMO。 - \- [绝对定位的DEMO](/demo/mobile/position/absolute.html) ### flexbox flexbox布局我估计是仿照**flex***布局方式。由于主流移动端都使用的现代浏览器都支持这个CSS3属性。Flexbox布局俗称伸缩布局,它可以简单快速的创建一个具有弹性功能的布局。由于移动多终端的需求,所以首选是flexbox。 - \- [flexbox的DEMO](/demo/mobile/position/flex.html) - \- [[译]flexbox全揭秘](http://css-tricks.com/snippets/css/a-guide-to-flexbox/) ## 图片与文字 ### 非背景图片 之前提到的[常见移动Web名词](https://www.zybuluo.com/hacke2/note/45079),设备像素比:2的高清视网膜技术却会使图片变得模糊,这是为什么呢?
home
中密度与超高密度(retina)显示的区别
根据计算公式,一个像素点会被拆分成4个小点,显示起来自然模糊了。 解决方案一般有两个: 1.设置`target-densitydpi=device-dpi`,采用按照真实比例来展示,然后进行媒体查询技术如下面代码: ```css #header { background:url (medium-density-image.png); } @media screen and (- webkit -device-pixel-ratio:1.5) { /* CSS for high-density screens */ #header { background:url (high-density-image.png);} } @media screen and (- webkit -device-pixel-ratio:0.75) { /* CSS for low-density screens */ #header { background:url (low-density-image.png);} } ``` 这样有一个弊端就是:需要为每一种分辨率书写单独的代码。 2. 假如需要100×100的图片,那么从设计稿(宽为640)上截取200×200的大小,但设置还是100*100。宽720的设计稿 ,为了满足显示像素为360的屏幕。这样就可以来只有一份设计稿只写一份代码了。 另外,多张图片的显示可以进行`canvas`的绘制,进行`GPU`渲染。。 ### 背景图片 background-size设置为高度,自适应宽度100%,这也是CSS3的属性。 ### 文字 px单位 传统PC端常用的px来设置大小。因为他比较稳定和精确。 em单位 浏览器中放大或缩放浏览页面时会存在一个问题,因为字体大小是固定了的。要解决这个问题,我们可以使用“em”单位。 em有如下特点: 1. em的值并不是固定的; 2. em会继承父级元素的字体大小。 rem单位 `rem`是CSS3的属性,和`em`一样,他的值是不固定的。区别在于他参考的是一个根元素的确定值。`em`是相对于其父元素来设置字体大小的,这样就会存在一个问题,进行任何元素设置,都有可能需要知道他父元素的大小,在我们多次使用时,就会带来无法预知的错误风险。而rem是相对于根元素`html`,这样就意味着,我们只需要在根元素确定一个参考值。 在了解了px,em,rem的区别后,我们可以进行如下设置: ```css html { font-size: 62.5%; } body { font-size: 14px; font-size: 1.4rem; } ``` 我们在写大小的时候通过一些简单的计算就可以了,比如的拿到的设计稿有一一部分为18px的文字,那我们在写代码的时候就可以写: ```css p : {font-size:18px;font-size:1.8rem}/*(1.8 x 10=18)*/ ``` ## 动画 在移动端不用过多考虑平台端的兼容性,完成动画也是借助CSS3的动画来实现。 在我看来,移动端动画优先选择为以下顺序: transition > Animation > js 而且最好使用translate3d强制设备进行`GPU`渲染,但也不能过度使用。 我们可以使用CSS3动画库animate.css玩完成常见的动画。更多关于CSS3动画的可以参考: - \- [CSS动画简介](http://www.ruanyifeng.com/blog/2014/02/css_transition_and_animation.html) ## 一些事件 移动端原生的最重要的事件`touch` : * touchstart * touchmove * touchend * touchcancel 其中,他们之间触发的先后顺序为: touchstart > touchmove > touchend > click 移动端click会延迟300ms,原因是他在等待判断是不是双击。当然,现在的一些框架解决了这个问题: - \- [fastclick](https://github.com/ftlabs/fastclick) - \- [tap.js](https://github.com/alexgibson/tap.js) 用这几个事件可以衍生出很多事件,比如左滑右滑,上下滑屏,放大,缩放等。详情可以看指尖上的JS系列。 - \- [指尖下的js ——多触式web前端开发之一:对于Touch的处理](http://www.cnblogs.com/pifoo/archive/2011/05/23/webkit-touch-event-1.html) - \- [指尖下的js ——多触式web前端开发之二:处理简单手势](http://www.cnblogs.com/pifoo/archive/2011/05/22/webkit-touch-event-2.html) - \- [指尖下的js —— 多触式web前端开发之三:处理复杂手势](http://www.cnblogs.com/pifoo/archive/2011/05/22/webkit-touch-event-3.html) 现在已经有一些封装了的框架: - \- [hammer.js](https://github.com/hammerjs/hammer.js) - \- [touch.js](https://github.com/Clouda-team/touch.code.baidu.com) 当然还有其他移动端专属的事件,比如: * 触摸事件 * 屏幕旋转事件 我用原生JS模仿了神马搜索搜出美团后的信息轮播: - \- [移动端访问](http://www.hacke2.cn/works/demo/sm-meituan/) ## 一些框架 移动端有一些较为成熟框架: - \- [JQuery Mobile](http://jquerymobile.com/) - \- [JQTouch](http://jqtouch.com/) 一些公司也有自己的框架 - \- [腾讯Pro](https://github.com/AlloyTeam/Pro) - \- [百度BlendUI](https://github.com/Clouda-team/BlendUI) 但更多公司选择使用一些基础的类库自己封装一些常见的交互,毕竟在移动端上流量真的是寸土必争。比如神马搜索用的是[zepto.js](http://zeptojs.com/)。通过gzip压缩后只有几k,而且风格与JQ一模一样,无学习成本。 当然还有些为工具框架 - \- [iscroll4](http://cubiq.org/iscroll-4) ## 参考资料 * [jtyjty99999收集移动端开发所需要的一些资源与小技巧](https://github.com/jtyjty99999/mobileTech) * [[译]flexbox全揭秘](http://www.cnblogs.com/lilyimage/p/3682810.html) * [移动端重构系列3——整体布局](http://www.w3cplus.com/mobile/mobile-terminal-refactoring-mobile-layout.html) * [移动端webapp开发必备知识](http://www.qianduan.net/mobile-webapp-develop-essential-knowledge.html) * [CSS3的REM设置字体大小](http://www.w3cplus.com/css3/define-font-size-with-css3-rem) * [设备像素比devicePixelRatio简单介绍](http://www.zhangxinxu.com/wordpress/2012/08/window-devicepixelratio/) * [使用CSS3开启GPU硬件加速提升网站动画渲染性能](http://blog.bingo929.com/transform-translate3d-translatez-transition-gpu-hardware-acceleration.html) **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2014-11-12-create-github-page.md ================================================ --- layout: post title: 30秒搭建Github Page description: "30秒创建和我一样的Github博客" tags: [分享] image: background: witewall_3.png comments: true share: true --- >如果中国每个程序员都写博客,那么中国IT届的春天就来了 有同学问我的网站是怎么创建的,其实30秒就可以办到。话不多说,大家屏住呼吸,看看能不能坚持30秒:D ## 第一步 fork 我的博客使用的`hpstr-jekyll-theme`,基于`jekyll`的,我们都知道,`Github`是支持`jekyll`的是进入`hpstr-jekyll-theme` 的。 Github 地址: [https://github.com/mmistakes/hpstr-jekyll-theme](https://github.com/mmistakes/hpstr-jekyll-theme) 点击Fork
home
单击Fork
## 第二步 修改
home
点击设置
home
将名字改为`你的Github名字.github.io`
### 完了吗?完成了!数一下到底有没有30秒?!
home
成功访问
等待几分钟,访问以下 你的Github名字.github.io ## 一些后续设置 现在你已经有了自己的博客了,当你还可能做以下事情: 1.将google cdn换成 baidu cdn 原因你懂的 2.去掉分享到twitter、facebook等国外社区,加入百度分享 3.加入百度站长助手,方便您的统计 4.配置disqus评论 5.加入Read More功能,不想像以前一样文章全显示出来 上述功能其实我已经给我自己做了配置,你也可以[Fork我的博客](https://github.com/hacke2/hacke2.github.io)。 期待您的文章! **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2014-11-14-answer-how-to-prepare.md ================================================ --- layout: post title: 还有一年时间,该如何准备? description: "回答群友问题" tags: [问答] image: background: witewall_3.png comments: true share: true --- >群中有朋友问还有一年时间,该如何准备?我回答了一下,仅仅是一孔之见 `群友:` >我现在css方面看了css那些事儿,css权威指南,正在看精通css;js方面看了javascript高级程序设计,javascript语言精粹;也做了一些页面效果的练习;这周拿了百度偏前端的开发测试实习,但是由于某些原因没办法去。我想知道如果我想明年暑假校招的时候能进BAT的前端部门,现在开始应该偏重看哪些知识点或者书籍?本人现在研二,相对于css更喜欢 js ,希望各位经验丰富的前辈指点迷津,真心喜欢 js 还有就是有必要学习学习 jquery 、nodejs、angularjs 什么吗? `重庆-球霸天:` 我其实也是运气好,没什么经验可谈,只是有几个建议: 1.多写DEMO,将你的作品挂到网上去。可以放在github上,[像我一样](http://www.hacke2.cn/works/),也可以[像豪哥一样](http://1.gitapp.sinaapp.com/pro/) 放在sae上,有免费的云豆可拿 还有京东云,都是免费的。个人感觉[更好的基础练习](http://www.fgm.cc/learn/) 2.多多关注一些前端大牛的最新动态(通过微博或微信),看看他们最近在干什么,行业有哪些最近比较关注的知识点。要了解目标公司前端动态,别像我去360面试还不知道成银是谁,他组织的奇舞团和编写的thinkjs.js很出名,如果当时和他聊得欢保证成功率在70%以上。 3.整理自己所学过的知识点,将之形成一个系列,放到博客中,将来面试的时候也展示一下,张鑫旭已经写了无数篇文章了,我在想如果我大一就坚持写博客现在得有多牛啊,其实我们这一行除了技术还有就是文字的产出能力。玉伯说的。 4.要是有项目经验再好不过了,在项目中碰见过过哪些问题,怎么解决的,担任什么样的角色,都是非常非常重要的。我在面试的时候问面试官对我的评价,他说我是他面过的同学对项目是**最深入的一个**。 5.想BAT不会问你有没有使用过这些框架,而会问你这些框架的源码你读过没,那个点是怎么实现的,比如我就被问过JQ和NG的,jq很定要会,如果以后走全栈Node.js要了解,ng可学可不学,因为2.0马上要出来了,与以前风格完全不一样,建议学学backbone(但他其实也不是传统意义的MVC)。 6.还有就是与时俱进,HTML5,CSS3,ES6,都不能比别人落后啊。 一年的时间真的还很长,我真正接触前端也就这么几个月,现在拿自己的水平和前一个月做一个比较,感觉真的不是一个档次。每个月都能感觉到自己的进步,这个感觉很好。祝好运。 最后引用豪哥的总结发言: >我们走过弯路之后给你的一个人生阶段的结果,人生可能确实需要走一些弯路之后才能真正明白过来人所给的建议。要不然,真不明白里边的坑有多深,需要填多深。 **end from [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2014-11-18-about-responsive.md ================================================ --- layout: post title: 响应式Web初级入门 description: "关于响应式编程的一些思考" tags: [移动Web] image: background: witewall_3.png comments: true share: true --- ## 跨终端时代的到来 当你乘坐各种交通工具(公交、地铁、轻轨、火车)时你会发现,人们都个个低下头在玩自己的手机、平板、Kindle,没错,你正在处于一个多终端设备的时代!手机用户连年上升,前几天我们在感叹以前玩沙包、陀螺,现在小孩的娱乐就是玩手机--。另外,微软的Xbox和任天堂的Wii等游戏设备也有自己的浏览器。设备真的来了。。 现在网站主流跨终端的有以下方式: ### 单域 比如[前端乱炖](http://www.html-js.com/)和[我的个人博客](http://www.hacke2.cn)都属于此一类。此类网站具有只编写一次就能实现跨终端的需求,维护起来相当方便。但缺点也很明显,加载不必要的JS和CSS比往常相比巨多。 单域还有一种情况,就是多个模板,你用移动设备可能访问一个网站,最底下会有访问桌面版,访问触屏版等,他会重新加载模板。
home
提示访问移动版
home
随之进入移动版,HTML结构也会发生变化
### 多域 如[神马搜索](http://m.sm.cn)和百度(http://www.baidu.com),当用桌面浏览器和移动浏览器访问的结果是不一样的。其中的手段可能有两种: * [Ngix反向代理判断](http://blog.csdn.net/fairplay_li/article/details/13769393) * 服务端直接判断UA输出不同的界面,[JAVA和PHP等后台语言都提供了支持框架](https://github.com/tobie/ua-parser) 两家都对移动端做了专门的页面,这样进行功能的拆减,用户体验当然大大提高,但有需求发生变化时,往往要更改两处地方。 ### 多终端 也就是前端最不想看到的,很多公司为了提高更好地用户体验都使用native开发,如IOS的OC,SWIFT,Andriod SDK等。功能强大,接口丰富,缺点就是更新起来异常困难,很多用户都不想过几天就安装一个APP。 **本文主要讲第一种`单域(响应式)`的情况** ### 响应式Web 响应式Web设计最早在2010年EthanMarcotte发表过一篇文章[《Responsive Web Design》](http://www.alistapart.com/articles/responsive-web-design/),**基本每本将响应式的书籍都将他提起,那个例子太经典**提起,文中援引了响应式建筑设计的概念: >最近出现了一门新兴的学科——"响应式建筑(responsive architecture)"——提出,物理空间应该可以根据存在于其中的人的情况进行响应。结合嵌入式机器人技术以及可拉伸材料的应用,建筑师们正在尝试建造一种可以根据周围人群的情况进行弯曲、伸缩和扩展的墙体结构;还可以使用运动传感器配合气候控制系统,调整室内的温度及环境光。已经有公司在生产"智能玻璃":当室内人数达到一定的阀值时,这种玻璃可以自动变为不透明,确保隐私。 澄清一点,**响应式站点不等同于移动站点**,他只是一种开发移动站点的策略。其实按照这个说法[神马搜索](http://m.sm.cn)是一款纯粹的移动WEB APP,因为它没有桌面版。
home
为移动而生,专注移动搜索的神马搜索
## 三种布局方式 现存的布局哪一种更加适合做响应式的网站呢?一般来说有以下三种布局: ### 固定布局 应该是新手开发人员最喜欢用的布局方式,简单粗暴,设计稿是多少PX,写CSS时就多少PX,对页面的控制力度是最强的,上下级没有联系,想调哪个就调那个,如果设置`box-sizing:border-box;`,甚至对整个布局都没有影响。 最常见的就是`body`使用`960px`的像素,有以下两个因素: * 适应正方形的老式屏幕; * 两边补白,让宽屏用户不觉得那么聚集; * 960可以被3、4、5、6、8、10、12、15整除。 [点击戳DEMO](/demo/2014-11/ali-px.html) 但问题是这么做毫无响应式可言,会出现很恶心的`横向滚动条`,另外,移动端的浏览器会默认的将网页缩小,根本`无多终端性`可言。 ### 流式布局 在流式布局中,度量单位不再是简单的像素,而是百分比。这使得页面具有可变性。 [点击戳DEMO](/demo/2014-11/ali-percent.html) 当缩小浏览器边框时,`万恶的横向滚动条`消失了。缺点就是有些文本的行宽会看起来太宽,而在小屏幕看起来太窄 当然,在良好的支持CSS3的移动浏览器下,使用`flex布局`更加有优势! ### 弹性布局 这次度量单位又变了,通常情况以em为单位,但是em太依赖于父级,好在CSS3提供了更好的`rem方式`(这个demo找的不好,因为table表格本来就有流动的属性`display:table;display:table-cell;`)。 [点击戳DEMO](/demo/2014-11/ali-rem.html) 其实细想一下,**选择布局方式其实是对`度量单位`的选择!** 另外还有`网格布局`方式,bootstrap就是采用12栅栏布局,另外前不久winner也谈了一些淘宝提供的可伸缩布局方案:[lib.flexible](https://github.com/amfe/lib.flexible),按照DEMO来看,其使用的是`rem`方式,在改变视窗大小的时候动态的改变基准的比例(浏览器默认是`16px`,设置器基准大小为`62.5%`)。还有人提出来的`混合布局`,但无论哪一种,都离不开上面`固定布局`,`流式布局`,`弹性布局`,三种的支撑。 综上所述,`流式布局`或`弹性布局`或许是响应式布局的更好方式。 ## 媒体查询 难道有`流式布局`或`弹性布局`就够了吗?就像一个屌丝升职加薪就够了?不当上CEO怎么赢取白富美? 媒体查询可以让你根据在特定环境下查询到的各种属性值-比如`分辨率`,`色彩深度`,`高度和宽度`(包括设备宽度与视觉宽度),`横向纵向`,`设备像素比`来决定应用什么样的样式。 我们可以看到我们上面的那些DEMO(我承认这个DEMO找的不好,是从今年阿里校招题目里面抠出来的),当我们改变浏览器大小时,左边的列表实在是太丑,但是使用媒体查询后的效果就不一样了。 [点击戳DEMO](/demo/2014-11/ali-media.html) ### 媒体查询的语法 语法很简单 @media [not|only] type [and] [expr] { rules } 解释一下: * not|only:逻辑关键字 * expr:媒体表达式 * type:媒体类型 * rules:CSS样式 1.逻辑关键字 有and,not,or,only等,前三个不多说,最后一个是因为很多较老的浏览器支持媒体类型,却不支持媒体查询,有时候导致浏览器去尝试下载那些你不希望用户看到的样式。 2.媒体表达式 表达式支持的也很多,这里也不列举了,重点有: * 表示**显示区域**的`width`和`height` * 表示**设备区域**的`device-width`和`device-height` * 表示**横屏还是竖屏**的`orientation` * 表示**设备像素比**`device-pixel-ratio` 3.媒体类型 媒体类型有很多,感兴趣下来查一下,这里就不列举了,一般大多网站设置的是screen,如不你嫌麻烦可以什么都不写(默认为all),支持所有设备。 4.规则 就是你想要在这个环境下想展示出的CSS 下面一个简单的示例: ```css @media sreen and (min-width: 320px) { } ``` 另外,媒体查询也可以使用在外部样式上,如: ```html ``` ### 关于断点 这里说的断点不是传统意义的调试断点,而是一些常用的`标准宽度`: * 320px(iPhone和其他一些设备) * 769px(iPad) * 1024 依赖这些断点会有一个问题,今天流行的明天未必流行,而且在断点过渡的时候会显得很突兀,所以,确定断点的一个原则是**追随内容**。让设计稿,内容来确定你的断点。
home
前端乱炖的断点
### 兼容性 一般的,在IE9一下,加载下列CSS ```html ``` ## 图片和视频 ### 图片 对于背景图片来说,CSS3有个属性为`background-size`,设置为100%就可以自适应,但是在小屏幕的移动设备加载大图片有点`杀鸡焉用宰牛刀`,一般为了加快速度,我们的策略是:`有选择性的加载图片`,一般会有以下四种方法: 1.万能的媒体查询 只加载与当前屏幕相匹配的图片 2.JS做判断 JS提供了一个方法:`window.matchMedia`,可以把CSS媒体查询作为参数传入,返回相关媒体查询是否匹配的信息。 使用方法: ```js if(window.matchMedia('(min-width:320px)').matches) { //其他代码 } ``` 我们可以利用他来加载合适的图片。 3.使用src.sencha.io `src.sencha.io`可以传入需要的尺寸和图片地址,自动来压缩图片,使用了CDN+缓存策略技术。当然,我们的服务端也可以自己来实现
home
图片已经被压缩到320px
[点击戳图片](http://src.sencha.io/320/http://www.hacke2.cn/images/shiyanshi.jpg) 4.SVG 对于图片伸缩的问题,也可以采用可伸缩矢量图(SGG)来解决。 有关SVG的资料请戳大漠的[w3cplusSVG标签](http://www.w3cplus.com/blog/tags/411.html?u=undefined%26t=%26msgfrom=%26area=msgtext%26clickfrom=3%26clickscene=)。 ### 视频 视频的方式与上面类似,可以使用`媒体查询`和`js matchMedia`,当然,用设备看视频的一般是WIFI下,所以直接使用`max-width:100%;height:auto;`也是可以的。如果是连接站外资源,如`优酷`难么,一般解决方法是放到一个iframe里面,详情请看[站外引用的优酷视频,怎样让视频高度自适应?](http://bbs.csdn.net/topics/390600207) ```js
``` ## 总结 写到这边,算是对响应式的一个初级入门吧,但个人觉得,做起来原理简单,但**如果设计一个响应式的网站?怎样保证他的高质量?怎么不会影响到速度?已有网站怎么改为相应式的?**这些才是真正值得我们研究的东西。 ## 扩展阅读 [CSS3媒体查询](http://www.w3.org/html/ig/zh/wiki/CSS3%E5%AA%92%E4%BD%93%E6%9F%A5%E8%AF%A2) [媒体查询简介——第1部分:什么是媒体查询?](http://www.infoq.com/cn/news/2011/12/introducing-media-queries) [什么是响应式Web设计?怎样进行?](http://www.chinaz.com/manage/2011/1121/221607_2.shtml) [通过CSS3 Media Query实现响应式Web设计](http://developer.51cto.com/art/201201/312206.htm) [移动优先的跨终端 Web](http://www.imooc.com/learn/43) [手机淘宝的flexible设计与实现](http://www.html-js.com/article/Like-the-winter-flexible-design-and-implementation-of-the-mobile-phone-Taobao-cold) **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2014-11-30-gugong.md ================================================ --- layout: post title: 故宫&国家博物馆之行 description: "北京第一站游记" tags: [游记] image: background: witewall_3.png comments: true share: true --- >前几天刚来北京,这几天找房子、买东西,做好该准备的工作后(谢谢猪肉在这几天的帮忙),就开始北京景点之旅。第一站选择`故宫`。 早晨11点来到天安门前,做了安检登上了`天安门`。
home
天安门前
以前在电视前看到的地方今天终于走了上去,等上`天安门`后,感受了当年伟人的视觉。当然,雾霾很严重远方看的还是很不清晰,但能容纳10万人的广场还是能看见。
home
天安门上
看完一些天安门的介绍,买了票和一张故宫的地图与介绍,然后正式进入紫禁城,一些地方真的是耳熟能详,什么`午门`,`太和殿`,`乾清宫`等等,传说中皇帝上早朝的地方没有想象中的大。 另外,故宫的防火意识挺强的,在很多房子旁边都放了一些`大缸子`,用来防火,而且怕冬天水会冻住,在缸底下也有烧火的。
home
用来防火的水缸
逛了几个展厅,不知不觉就快3小时了,看了看地图,还有几处地方漏了,只能回去再逛一遍,返回的途中看到一个很有特色的巷子,人很少,周围全是`红墙黄瓦`,YY了一下当年太监在这条路上跑路的样子。
home
人很少的巷子
在停止买票前进入了锡庆门,看到了传说中的`九龙墙`
home
传说中的九龙墙
后来又看了几个展馆和`珍妃井`,就走出了`神武门`。门口有卖`冰糖葫芦`的,买了一个,真心好吃。
home
北京的冰糖葫芦
后来想去`什刹海`,`后海公园`,因为下起雨来就回了,第二天继续这个地方,打算看看广场附近的地方就去还没去的地方,就是这么任性,来到了`天安门广场`。风真的好大。
home
天安门广场
结果去的迟了,毛主席纪念堂已经关闭,只能下次再来了。移步到旁边的`国家博物馆`,博物馆内东西太多了,为了细细观赏和感受,所以全程没有照相。 人类历史太久远了,我们不过是滚滚历史长河的一颗小沙粒,想想有点伤感,出来又快6点了,吃了个饭,风太大,就回去了,下次还会来这边,北京能逛得地方是在太多了! **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2014-11-8-arrow-functions-and-their-scope.md ================================================ --- layout: post title: 译-ES6箭头函数和它的作用域 description: "关于ES6里箭头函数及其作用域的使用" tags: [翻译] image: background: witewall_3.png comments: true share: true --- >http://es6rocks.com/2014/10/arrow-functions-and-their-scope/ 原文链接 摇滚ES6中国站快要上线了,大家期待吧,也可以联系[我](https://github.com/hacke2)或者[ES6组织](https://github.com/es6rocks)为这个活动做出点贡献! 在ES6很多很棒的新特性中, 箭头函数 (或者大箭头函数)就是其中值得关注的一个! 它不仅仅是很棒很酷, 它很好的利用了作用域, 快捷方便的在现在使用以前我们用的技术, 减少了很多代码......但是如果你不了解箭头函数原理的话可能就有点难以理解. 所以,让我们来看下箭头函数, 就是现在! ## 执行环境 你可以自己去学习和尝试下, 你可以简单的把示例程序代码复制到你的浏览器控制台下. 现在, 推荐使用Firefox(22+)开发者工具, Firefox(22+)开发者工具现在支持箭头函数,你也可以使用谷歌浏览器. 如果你使用谷歌浏览器, 你必须要做下列两件事: - \- 在谷歌浏览器中地址栏中输入:"about:flags", 找到 "使用体验性Javascript"选项,开启使用。 - \- 在函数的开头加上"use strict",然后再在你的谷歌浏览中测试箭头函数吧(提示:请用谷歌浏览器v38,我当时就是被浏览器版本坑了): ```js (function(){ "use strict"; // use arrow functions here }()); ``` 幸运的是后面会有越来越多的浏览器支持ES6特性. 现在你完成了所有准备工作, 让我们继续深入它吧! ## 一个新话题 最近大家在讨论关于ES6的一个话题:关于箭头函数, 像这样: ```js => ``` ## 新的语法 随着讨论产生了一个新的语法: ```js param => expression ``` 新增的语法是作用在变量上, 可以在表达式中申明多个变量, 下面是箭头函数的使用模式: ```js // 一个参数对应一个表达式 param => expression;// 例如 x => x+2; // 多个参数对应一个表达式 (param [, param]) => expression; //例如 (x,y) => (x + y); // 一个参数对应多个表示式 param => {statements;} //例如 x = > { x++; return x;}; // 多个参数对应多个表达式 ([param] [, param]) => {statements} // 例如 (x,y) => { x++;y++;return x*y;}; //表达式里没有参数 () => expression; //例如var flag = (() => 2)(); flag等于2 () => {statements;} //例如 var flag = (() => {return 1;})(); flag就等于1 //传入一个表达式,返回一个对象 ([param]) => ({ key: value }); //例如 var fuc = (x) => ({key:x}) var object = fuc(1); alert(object);//{key:1} ``` ## 箭头函数是怎么实现的 我们可以把一个普通函数转换成用箭头函数来实现: ```js // 当前函数 var func = function (param) { return param.split(" "); } // 利用箭头函数实现 var func = param => param.split(" "); ``` 从上面的例子中我们可以看出箭头函数的语法实际上是返回了一个新的函数, 这个函数有函数体和参数 因此, 我们可以这样调用刚才我们创建的函数: ```js func("Felipe Moura"); // returns ["Felipe", "Moura"] ``` ## 立即执行函数(IIFE) 你能在立即执行函数里使用箭头函数,例如: ```js ( x => x * 2 )( 3 ); // 6 ``` 这行代码产生了一个临时函数,这个函数有一个形参`x`,函数的返回值为`x*2`,之后系统会马上执行这个临时函数, 将`3`赋值给形参`x`. 下面的例子描述了临时函数体里有多行代码的情况: ```js ( (x, y) => { x = x * 2; return x + y; })( 3, "A" ); // "6A" ``` ## 相关思考 思考下面的函数: ```js var func = x => { return x++; }; ``` 我们列出了一些常见的问题: **- 箭头函数创建的临时函数的`arguments`是我们预料的那样工作** ```js console.log(arguments); ``` **- `typeof`和`instanceof`函数也能正常检查临时函数** ```js func instanceof Function; // true typeof func; // function func.constructor == Function; // true ``` **- 把箭头函数放在括号内是无效的** ```js // 有效的常规语法 (function (x, y){ x= x * 2; return x + y; } (3, "B") ); // 无效的箭头函数语法 ( (x, y) => { x= x * 2; return x + y; } ( 3, "A" ) ); // 但是可以这样写就是有效的了: ( (x,y) => { x= x * 2;return x + y; } )( 3,"A" );//立即执行函数 ``` **- 尽管箭头函数会产生一个临时函数,但是这个临时函数不是一个构造函数** ```js var instance= new func(); // TypeError: func is not a constructor ``` **- 同样也没有原型对象** ```js func.prototype; // undefined ``` ## 作用域 这个箭头函数的作用域和其他函数有一些不同,如果不是严格模式,`this`关键字就是指向`window`,严格模式就是`undefined`,在构造函数里的`this`指向的是当前对象实例,如果this在一个对象的函数内则`this`指向的是这个对象,`this`有可能指向的是一个`dom元素`,例如当我们添加事件监听函数时,可能这个`this`的指向不是很直接,其实`this`(不止是`this`变量)变量的指向是根据一个规则来判断的:作用域流。下面我将演示`this`在事件监听函数和在对象函数内出现的情况: 在事件监听函数中: ```js document.body.addEventListener('click', function(evt){ console.log(this); // the HTMLBodyElement itself }); ``` 在构造函数里: ```js function Person () { let fullName = null; this.getName = function () { return fullName; }; this.setName = function (name) { fullName = name; return this; }; } let jon = new Person(); jon.setName("Jon Doe"); console.log(jon.getName()); // "Jon Doe" //注:this关键字这里就不解释了,大家自己google,badu吧。 ``` 在这个例子中,如果我们让Person.setName函数返回Person对象本身,我们就可以这样用: ```js jon.setName("Jon Doe") .getName(); // "Jon Doe" ``` 在一个对象里: ```js let obj = { foo: "bar", getIt: function () { return this.foo; } }; console.log( obj.getIt() ); // "bar" ``` 但是当执行流(比如使用了setTimeout)和作用域变了的时候,this也会变。 ```js function Student(data){ this.name = data.name || "Jon Doe"; this.age = data.age>=0 ? data.age : -1; this.getInfo = function () { return this.name + ", " + this.age; }; this.sayHi = function () { window.setTimeout( function () { console.log( this ); }, 100 ); } } let mary = new Student({ name: "Mary Lou", age: 13 }); console.log( mary.getInfo() ); // "Mary Lou, 13" mary.sayHi(); // window ``` 当`setTimeout`函数改变了执行流的情况时,`this`的指向会变成全局对象,或者是在严格模式下就是`undefine`,这样在`setTimeout`函数里面我们使用其他的变量去指向`this`对象,比如`self`,`that`,当然不管你用什么变量,你首先应该在setTimeout访问之前,给`self`,`that`赋值,或者使用`bind`方法不然这些变量就是undefined。 这是后就是箭头函数登场的时候了,它可以保持作用域,this的指向就不会变了。 让我们看下上文**起先**的例子,在这里我们使用箭头函数: ```js function Student(data){ this.name = data.name || "Jon Doe"; this.age = data.age>=0 ? data.age : -1; this.getInfo = function () { return this.name + ", " + this.age; }; this.sayHi = function () { window.setTimeout( ()=>{ // the only difference is here console.log( this ); }, 100 ); } } let mary = new Student({ name: "Mary Lou", age: 13 }); console.log( mary.getInfo() ); // "Mary Lou, 13" mary.sayHi(); // Object { name: "Mary Lou", age: 13, ... } ``` >分析:在sayHi函数中,我们使用了箭头函数,当前作用域是在student对象的一个方法中,箭头函数生成的临时函数的作用域也就是student对象的sayHi函数的作用域。所以即使我们在setTimeout调用了箭头函数生成的临时函数,这个临时函数中的this也是正确的指向。 ## 有趣和有用的使用 创建一个函数很容易,我们可以利用它可以保持作用域的特征: 例如我们可以这么使用:Array.forEach() ```js var arr = ['a', 'e', 'i', 'o', 'u']; arr.forEach(vowel => { console.log(vowel); }); ``` >分析:在forEach里箭头函数会创建并返回一个临时函数 tempFun,这个tempFun你可以想象成这样的:function(vowel){ console.log(vowel);}但是Array.forEach函数会怎么去处理传入的tempFunc呢?在forEach函数里会这样调用它:tempFunc.call(this,value);所有我们看到函数的正确执行效果。 map里使用箭头函数,这里我就不分析函数执行过程了。。。。 ```js var arr = ['a', 'e', 'i', 'o', 'u']; arr.map(vowel => { return vowel.toUpperCase(); }); // [ "A", "E", "I", "O", "U" ] ``` 费布拉奇数列 ```js var factorial = (n) => { if(n==0) { return 1; } return (n * factorial (n-1) ); } factorial(6); // 720 ``` 我们也可以用在Array.sort方法里: ```js let arr = ['a', 'e', 'i', 'o', 'u']; arr.sort( (a, b)=> a < b? 1: -1 ); ``` 也可以在事件监听函数里使用: ```js document.body.addEventListener('click', event=>console.log(event, this)); // EventObject, BodyElement ``` ## 推荐的链接 下面列出了一系列有用的链接,大家可以去看一看 - \- [Arrow Functions in MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) - \- [TC39 Wiki about Arrow Function](http://tc39wiki.calculist.org/es6/arrow-functions/) - \- [ESNext](https://github.com/esnext) - \- [ES6 Tools](https://github.com/addyosmani/es6-tools) - \- [Grunt ES6 Transpiler](https://www.npmjs.org/package/grunt-es6-transpiler) - \- [ES6 Fiddle](http://www.es6fiddle.net/) - \- [ES6 Compatibility Table](http://kangax.github.io/compat-table/es6/) ## 总结 尽管大家可能会认为使用箭头函数会降低你代码的可读性,但是由于它对作用域的特殊处理,它能让我们能很好的处理this的指向问题。箭头函数加上let关键字的使用,将会让我们javascript代码上一个层次!尽量多使用箭头函数,你可以再你的浏览器测试你写的箭头函数代码,大家可以再评论区留下你对箭头函数的想法和使用方案!我希望大家能享受这篇文章,就像你会不就的将来享受箭头函数带给你的快乐. **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2014-12-2-fe-cut-image.md ================================================ --- layout: post title: 实习初体验之前端切图 description: "前端切图的一些小技巧" tags: [PS] image: background: witewall_3.png comments: true share: true --- >以前在实验室做项目图都是美工同学给切好的,结果。。不会,给我一个PSD我竟然不会切--,让同事演示了一下太快了,没好意思再问,最后找`小娟同学`语音演示了一把,才算了解个大概。 首先,我们把PSD加载到PS中。
home
用PS CS5打开psd文件
用左侧放大镜放大后,点击左侧工具栏,选择自动选择,选择图层
home
如图选择好
选择好那个暂停图标,右侧图层出现了要切出的图片,可以点击那个`眼睛`试一下,点一下如果消失就OK。
home
我们已经找到了要切的
然后我们点击右键
home
选择复制图层
选择目标文档为`新建`,名称起为`stop`
home
设置好点击确定
而后会出来一个透明的图层,然后我们将之前选择的图层进行裁剪。使用快捷键: `Alt + I +R `
home
出现一个弹框点击确定
现在,我们已经将那个待切除的图片准备好了
home
那一个小小的暂停按钮
然后,我们要将这个图片保存到本地,使用快捷键: `Ctrl + Alt + R + S ` 弹出一个对话框,选择png24,点击存储。
home
保存我们的小图标
关于选择图片格式阿里UED有个不错的文章:[图片格式与设计那点事儿](http://ued.taobao.org/blog/2010/12/jpg_png/) 这样就完成了切图工作。 如何切多个图呢?在选择图层的时候按住`shift`就可以了。 **反思:**该做的还得做,欠下的还是要还的。 **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2014-12-8-my-university-experience.md ================================================ --- layout: post title: 给学弟学妹说的话 description: "我的大学经历" tags: [感悟] image: background: witewall_3.png comments: true share: true --- >今天学弟找到我,让我说说大学里的经历,给以后的学弟学妹们可以参考一下。说我其实没啥东西留下的,的确有点装逼了,我确实比较幸运,现在在一个条件相对不错的公司实习,那我就说一些吧。 ## 兴趣是最好的老师 有没有兴趣是看你能不能在一行业持久下去的最关键因素。我不相信对IT,没有一点兴趣的人会成为一个巨匠。但是有些同学可能之前没有接触过计算机,没有兴趣,那就强迫自己爱上他。找些相关纪录片,电影看看,看看那些牛人的历程,找一个计算机相关专业的男友,或许你就会爱上计算机:D,如果真的没有兴趣,**至少要找一个以后能养家糊口的本事**。 有了兴趣你可能会加入一些实验室,组织等,会寻求那些与你志同相合的人,这样,`人脉`也有了 ## 积累与分享 有了兴趣后,你自己就会不由自主的动起手来了,码点小玩意,分享点小技巧,写点小博客,**每一步都是你的脚印,也是你未来的谈判筹码**。当你花上几年时间看看以前走的脚印,你会感动到自己。 积累和分享最好的手段就是写博客了,以下是我这两年的积累: * [我以前CSDN的博客 http://blog.csdn.net/hacke2](http://blog.csdn.net/hacke2) * [我现在Github的博客 http://www.hacke2.cn/](http://www.hacke2.cn/) `最好的阅读是输出`,将自己所学习到的东西梳理以下,形成文字,再重新认知一遍,你会理解的更加深入。有个程序员听完网上视频后就当场敲一遍,不会了再看看那视频,然后第二天再敲一遍,再一次巩固,获取敲得过程不是很顺利,但是几年坚持下来,真心强大。 还可以加一些高质量的QQ群,在一些论坛上活跃活跃,帮助他人自己也学到了东西,很多都是一线企业的第一手信息,而且顺带混个脸熟:D。 ## 实际的项目 如果你能加入实验室,做真实的项目是非常幸运的,但由于实验室空间有限,如果你没能进入实验室也不要紧,跟着其他老师做(如`徐传云老师`,`刘亚辉老师`,`李刚老师`)、其他组织,如我校的`CFC`,或者寒暑假在外面公司实习,或是网上结识伙伴做外包、参加开源项目等。**有些人只会在院长信箱抱怨,而有些人自己寻求出路。** ## 一句话 其他的不说了,谋事在人,成事在天,有些坑必须得自己趟一下才服气,最好送上一句话,祝学弟学妹有一个好的未来: **不该在最应该努力的年龄选择安逸,不被嘲笑的梦想,没有实现的价值** **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2014-8-29-hello-memory-leak.md ================================================ --- layout: post title: JavaScript内存泄漏 description: "进入WEB 2.0 时代,js人们对Web应用有了高更的要求.一个页面很可能数小时不会发生URL跳转,并同时通过Web服务动态的更新页面内容.复杂的事件关联设计、基于对象的JScript和DHTML技术的广泛采用,使得代码的能力达到了其承受的极限.在这样的情况和改变下,弄清楚内存泄露方式变得非常的急迫。那么,为什么会出现内存溢出呢?" tags: [JavaScript, 内存泄漏] image: background: witewall_3.png comments: true share: true --- # 简介 所谓内存泄露就是内存空间使用完毕之后未回收。在过去Web开发人员并没有太多的去关注内存泄露问题.那时的页面间联系大都比较简单,js使用的功能主要是表单校验,不会有太多关于特效以及业务等方面上的扩展,而进入WEB 2.0 时代,js人们对Web应用有了高更的要求.一个页面很可能数小时不会发生URL跳转,并同时通过Web服务动态的更新页面内容.复杂的事件关联设计、基于对象的JScript和DHTML技术的广泛采用,使得代码的能力达到了其承受的极限.在这样的情况和改变下,弄清楚内存泄露方式变得非常的急迫。那么,为什么会出现内存溢出呢? # 内存分配与垃圾回收 说道内存泄露,就不得不谈到内存分配的方式。内存分配有三种方式,分别是: ## 静态分配(Static Allocation ) 静态变量和全局变量的分配形式。如果把房间看做一个程序,我们可以把静态分配的内存当成是房间里的耐用家具。通常,它们无需释放和回收,因为没人会天天把大衣柜当作垃圾扔到窗外。 ## 自动分配( Automatic Allocation ) 在栈中为局部变量分配内存的方法。栈中的内存可以随着代码块退出时的出栈操作被自动释放。 这类似于到房间中办事的人,事情一旦完成,就会自己离开,而他们所占用的空间,也随着这些人的离开而自动释放了。 ## 动态分配( Dynamic Allocation ) 在堆中动态分配内存空间以存储数据的方式。也就是程序运行时用malloc或new申请的内存,我们需要自己用free或delete释放。动态内存的生存期由程序员自己决定。一旦忘记释放,势必造成内存泄露。这种情况下,堆中的内存块好像我们日常使用的餐巾纸,用过了就得扔到垃圾箱里,否则屋内就会满地狼藉。因此,懒人们做梦都想有一台家用机器人跟在身边打扫卫生。在软件开发中,如果你懒得释放内存,那么你也需要一台类似的机器人——这其实就是一个由特定算法实现的垃圾收集器。而正是垃圾收集机制本身策略的一些缺陷,导致了javascript内存泄漏。具体到浏览器中的实现,通常有以下两个策略: ### 标记清除 Javascript最常用的垃圾回收方式是“标记清除”(mark-and-sweep)。当变量进入环境(如在函数中声明了一个变量),则为其标记为“进入环境”;当变量离开环境,则为其标记为“离开环境”。垃圾回收器会定时扫描那些“离开环境”的变量,销毁那些被标记的值并回收它们所占的内存。 除了IE7以前的版本、Netscape Navigate3.0,其他版本的浏览器都用的是标记清除的垃圾回收策略。 ### 引用计数 引用计数的含义是跟踪记录每个值被引用的次数,如当声明一个变量并将一个引用类型赋给该变量时,这个值的引用次数就是1,如果这个引用类型赋给另一个变量,它的引用类型加1,。相反,如果第一个变量又引用了其他引用类型时,之前的引用类型的引用次数就减1,直到减成0。当垃圾回收下一次运行的时候,就会释放掉引用次数为0的引用类型的内存。 该方式有一个严重的问题,请看下面例子: ```js function problem(){ var objA = {}; var objB = {}; objA.someOtherObject = objB; objB.anotherObject = objA; } ``` objA、objB通过各自的属性相互引用,如果采用引用计数,objA和objB会一直存在。假使这个函数多次被调用,大量内存不能回收,直接导致内存泄漏。 # 常见内存泄漏及解决 ## 循环引用(Circular References) IE浏览器的COM组件产生的对象实例和网页脚本引擎产生的对象实例相互引用,就会造成内存泄漏。这也是Web页面中我们遇到的最常见和主要的泄漏方式;
例: ```js var element = document.getElementById(“some-element”); var myobject = {}; myobject.element = element; element.someElement = myobject; ``` 在此例中DOM元素与一个原生javascript对象形成循环引用,其中myobject.element指向element元素,element.someElement指向element对象,由于存在这个循环引用,即使将该DOM从页面中移除,它也永远不会被回收。为了避免该问题,最好在使用完毕后手动将其移除: ```js var element = document.getElementById(“some-element”); var myobject = {}; myobject.element = element; element.someElement = myobject; //当使用完后 myobject.element = null; element.someElement = null; ``` 为了解决上述问题,IE9以后将所有的BOM和DOM都转换成了javascript对象,这样,就避免了两种垃圾回收机制都存在的问题,也就消除了常见的内存泄漏的问题。 ## 内部函数引用(Closures) Closures也就是闭包,外部变量可以应用内部函数的局部变量,当外部变量一直引用,那么该局部变量会在内存中一直存在,如果有大量这样的情况,则可能会出现内存泄漏;
例: ```js function closures() { var a = 10; return function () { return a; } } var b = closures()(); ``` 在此例中,closures函数下有个闭包,返回了该函数的局部变量a,外部有一个变量b引用了a,则如果b不释放a,a会一直存在于内存中,解决方法就是在在b使用完后,主动的释放b: ``` function closures() { var a = 10; return function () { return a; } } var b = closures()(); //b使用完后 b = null; ``` >原文摘自《Web前端案例教材》 ---重庆理工大学创新实验室 end ================================================ FILE: _posts/2014-8-29-web-development-process.md ================================================ --- layout: post title: 高大上Web前端开发环境 description: "前几天做阿里笔试题,最后一个问题是:在前端开发中,经常会遇到调用后端接口的情况,如果我们不想依赖后台的开发环境,比如:本地搭建熟悉的环境,模拟AJAX,说出你的解决方案 记得我当时是这样答的:" tags: [JavaScript] image: background: witewall_3.png comments: true share: true --- # 起因 前几天做阿里笔试题,最后一个问题是:在前端开发中,经常会遇到调用后端接口的情况,如果我们不想依赖后台的开发环境,比如:本地搭建熟悉的环境,模拟AJAX,说出你的解决方案 记得我当时是这样答的: ### 若java 1.使用tomcat环境 2.使用selvet ```js protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getOutputStream().write("{status:'ok',value:'11'}".getBytes("UTF-8")); resp.setContentType("text/json; charset=UTF-8"); } ``` ### 若php 1.使用wamp 环境 2.后端PHP构造json数据 ```php echo "{status:'ok',value:'11'}"; //或者 echo json_encode($result);//$result 是数组 ``` ### 若对数据没有动态提取的要求,则直接放大xxx.json里面 json格式为 ```json {'status':'ok','value':'11'} ``` * 前台使用AJAX请求 ```js function ajax(url, success, fail){ // 1. 创建连接 var xhr = null; if(window.XMLHttpRequest){ xhr = new XMLHttpRequest() } else { xhr = new ActiveXObject('Microsoft.XMLHTTP'); } // 2. 连接服务器 xhr.open('get', url, true) // 3. 发送请求 xhr.send(null); // 4. 接受请求 xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ if(xhr.status == 200){ success(xhr.responseText); } else { // fail fail && fail(xhr.status); } } } } //请求到数据 var url = 'json.php'...其他地址 ajax(url, function(data) { data.status }) ``` 后来自己又想了一下,其实这道题就看你平时前端开发的环境是怎样的,因为我是JAVA出生,所以一想就想到了启动一个tomcat来启动一个服务器,上面开启一个sevlet来输出json数据。 总感觉这样太“重量级”了,在网上查了一查,才知道了阿里真实的意图--。 ### 先不说阿里意图是什么,先看看现在高大上的前端开发环境: * 代码编辑工具 * 断点调试工具 * 版本管理工具 * 代码合并和混搅工具 * 依赖管理工具 * 单元测试工具 * 集成测试工具 你没看错,这不是后端开发环境,这竟然是前端的开发环境! 下面,我们就来说说这些工具有哪些,在JAVA里面对应那些工具 未完待续... ================================================ FILE: _posts/2014-8-31-javascript-quiz.md ================================================ --- layout: post title: Javascript Quiz description: "Javascript Quiz 练习题解析" tags: [JavaScript] image: background: witewall_3.png comments: true share: true --- ### 1.考点:arguments的类型 ```js (function(){ return typeof arguments; })(); ``` 这个题考的是,乍一看arguments可能是一个数组,因为一般可以这样取到:arguments[0]、arguments[1]、arguments[2] 可其实他是类似于jQuery对象的方式,用类来模拟数组,类似于这样的: ```js var arguments = { 0 : '第一个参数', 1 : '第二个参数', 2 : '第三个参数', length : 3, //其他属性或方法 } ``` 所以结果是 'object',默认不为undefined ### 2.考点:函数的内部属性name ```js var f = function g(){ return 23; }; typeof g(); ``` g可选可不选,如果加上,则为函数的内部属性,如调用f.name 输出 g,直接调用会说g未定义 ### 3.考点:delete ```js (function(x){ delete x; return x; })(1); ``` delete 会删除对象的属性,如var a ={ b : 1}; delete a.b;此时打印a为{},但是不会删除形参,输出为1 ### 4.考点:赋值顺序 ```js var y = 1, x = y = typeof x; x; ``` 基本所有语言都赋值都是从右向左赋值,typeof x得到结果为'undefined',最终赋给y和x,打印出来为'undefined' ### 5.考点:typeof ```js (function f(f){ return typeof f(); })(function(){ return 1; }); ``` typeof的运算数未定义,返回的就是 "undefined". 运算数为数字 typeof(x) = "number" 字符串 typeof(x) = "string" 布尔值 typeof(x) = "boolean" 对象,数组和null typeof(x) = "object" 函数 typeof(x) = "function" 函数返回为1,输出'number' ### 6.考点:typeof ```js var foo = { bar: function() { return this.baz; }, baz: 1 }; (function(){ return typeof arguments[0](); })(foo.bar); ``` 和上一题类似,将bar函数传进去,arguments[0]指向foo.bar函数,函数指向,但此时this是window对象,window对象下午baz,所以是'undefined' 若将题目改为以下,则返回'number' ```js var foo = { bar: function() { return this.baz; }, baz: 1 }; (function(){ return typeof arguments[0].call(foo); })(foo.bar,foo); ``` ### 7.考点:作用域 ```js var foo = { bar: function(){ return this.baz; }, baz: 1 } typeof (f = foo.bar)(); ``` 原因参见上题,输出'undefined' ### 8.考点:逗号表达式 ```js var f = (function f(){ return "1"; }, function g(){ return 2; })(); typeof f; ``` 输出为 'function' , f执行的结果为函数表达式里最后一个,如在函数表达式再加一个,function h(){return 3},则输出3 如果为非函数则为少一个立即执行函数以下代码,输出b ```js var f = ('a', 'b'); console.log(f); ``` ### 9.考点:JavaScript加性操作符 ```js var x = 1; if (function f(){}) { x += typeof f; } x; ``` 参考《javascript 高级程序设计 第三版》 3.5.5章,输出为字符串'1undefined' ### 10.考点:typeof ```js var x = [typeof x, typeof y][1]; typeof typeof x; ``` 'string'的typeof 肯定也是'string', 输出为字符串'string' ### 11.考点:不知道考的是啥,文字游戏吧 ```js (function(foo){ return typeof foo.bar; })({ foo: { bar: 1 } }); ``` 输出'undefined',如果return typeof foo.foo.bar;为期望值 ### 12.考点:函数提升 ```js (function f(){ function f(){ return 1; } return f(); function f(){ return 2; } })(); ``` 函数提升了两次,第二次把第一次覆盖了,所以 return 后面的 f 是 return 语句的下一条语句声明的函数 f . ### 13.考点:实例化对象 ```js function f(){ return f; } new f() instanceof f; ``` 如果函数无返回值,则返回一个对象的实例,若果有返回值,则为改返回值,输出为false ### 14.考点:with ```js with (function(x, undefined){}) length; ``` whth改变了函数的作用域,其实里面的length为函数function(x, undefined){}.length, 函数length表示给函数真正传的参数的个数,为2 >以上题目来源:http://perfectionkills.com/javascript-quiz/ ================================================ FILE: _posts/2014-9-1-cqut-paging.md ================================================ --- layout: post title: JavaScript模仿实验室分页组件 description: "实验室分页组件,一个简单的分页,非AJAX局部刷新" tags: [小练习, JavaScript] image: background: witewall_3.png comments: true share: true --- # 起因 刚才蔡哥让我重启一下邻水项目服务器,我顺便有回顾了一下去年做的项目。。当时我的任务是园区动态+配置管理,园区动态里有个分页 当时是拿实验室以前项目:泛教育分页组件做的,完全是拿来主义,现在看到,就像用javascript实现一下,没什么技术含量,设计思路也是 至少7年前的,直接一个a标签打开一个连接,将你的当前页数传到后台去。什么时候再做一个AJAX的。 # 分析 邻水分页如下图: 邻水分页 现在数据还不是很多,显示了三条,而且有首页、尾页、上页、下页等辅助按钮,当当前页为第一个时,上页和首页成不可点击状。 当当前页为最后一个时,下页和尾页成不可点击状。 还有以下规则: * 如果总页数大于10且当前页远离总页数(小于5),则显示5个,后面的省略直到最后一个; * 总页数大于10且当前页接近总页数(小于总页数-3)则显示后4个; * 除开上面两个情况,显示当前页前后2页 # 代码 将page传到后台去取数据,一页显示多少条也在后台弄吧,用a数组存a标签,比字符串拼接快很多,StringBuffer也是按照这个原理实现的。 ```js function iPage(obj,count,curPage){ var href = 'article.do?page='; var obj=obj; var count=count; var curPage=curPage; var a=[]; //总页数少于10 全部显示,大于10 显示前3 后3 中间3 其余.... if(curPage == 1) { a.push('首页'); a.push('上页'); } else { a.push('首页'); a.push('上页'); } //总页数小于10 if(count<=10){ for(var i=1;i<=count;i++){ createPage(i); } }else { if(curPage <= 4) {//总页数大于10且当前页远离总页数(小于5) for(var i = 1; i <=5; i++) { createPage(i); } a.push('...'+count+'') }else if(curPage>=count-3){//总页数大于10且当前页接近总页数(小于总页数-3) a.push('1'); for(var i=count-4;i<=count;i++){ createPage(i); } }else{ //除开上面两个情况 a.push('1...'); for(var i=curPage-2;i<=curPage+2;i++){ createPage(i); } a.push('...'+count+''); } } if(curPage==count){ a.push('下页'); a.push('尾页'); } else{ a.push('下页'); a.push('尾页'); } obj.innerHTML=a.join(""); //生成页面 function createPage(i){ if(curPage==i){ a.push(''+i+''); } else{ a.push(''+i+''); } } } ``` ## 查看完整DEMO 我的分页 end ================================================ FILE: _posts/2014-9-1-oschina-angularjs.md ================================================ --- layout: post title: 开源中国的 AngularJS 优秀文章汇总 description: "在开源中国看到的,每一篇都是精华" tags: [小练习, JavaScript] image: background: witewall_3.png comments: true share: true --- >Angular JS (Angular.JS) 是一组用来开发Web页面的框架、模板以及数据绑定和丰富UI组件。它支持整个开发进程,提供web应用的架构,无需进行手工DOM操作。 AngularJS很小,只有60K,兼容主流浏览器,与 jQuery 配合良好。 angularJS end ================================================ FILE: _posts/2014-9-10-level-img-change.md ================================================ --- layout: post title: 带层次感的图片轮播 description: "带层次感的图片轮播,JavaScript小练习" tags: [JavaScript, 小练习] image: background: witewall_3.png comments: true share: true --- # 起因 豪哥的JS练习又一波来袭~今天又写了个百度爱玩的东西,暂且叫他带层次感的图片轮播吧
home
图片可以很有层次感的切换
# 思路 主要思路有二 ## 图片大小、位置的计算 我的思路是这样的: 因为首尾切换,我想到的就是自己封装一个循环队列 ```js //封装一个循环队列 function CircularQueue(arr) { this.arr = arr || []; } //移除前一个,追加到最后 CircularQueue.prototype.shift = function() { var temp = this.arr.shift(); this.arr.push(temp); } //移除最后一个,追加到头部 CircularQueue.prototype.unshift = function() { var temp = this.arr.pop(); this.arr.unshift(temp); } //添加一个元素 CircularQueue.prototype.add = function(obj) { this.arr.push(obj); } ``` 现在一个数组里缓存下初始位置的大小, ```js //初始化ARR数组 var queue = new CircularQueue(); for (var i = 0; i < li.length; i++) { queue.add({ top: parseInt(getStyle(li[i], 'top')), left: parseInt(getStyle(li[i], 'left')), width: parseInt(getStyle(li[i], 'width')), height: parseInt(getStyle(li[i], 'height')), zIndex: getStyle(li[i], 'z-index') }); } ``` 然后在每一个选择项加上mouseover事件,来切换以上图片。 ```js //绑定事件 for (var j = 0; j < links.length; j++) { links[j].onmouseover = (function(j, len) { return function() { //鼠标一上去小点切换 var k = len - 1; for (; k >= 0; k--) { links[k].className = 'dot'; } links[j].className += ' dot-active'; //替换大图片 var arr = getMiddleArr(j); updateStyle(arr); } })(j, links.length); } ``` 然后通过传入一个数值,得到以改数组为中心的新数组。 因为不能打乱以前的数组,所以我用了深克隆 ```js //深克隆 Object.prototype.clones = function() { var o = {}; for (var i in this) { o[i] = this[i]; } return o; }; Array.prototype.clones = function() { var arr = []; for (var i = 0; i < this.length; i++) if (typeof this[i] !== 'object') { arr.push(this[i]); } else { arr.push(this[i].clones()); } return arr; }; ``` ## 缓冲运动 还是用的智联社的运动库,最后将页面元素的样式更新 ```js //调用动作函数绘制 function updateStyle(arr) { for (var i = 0; i < li.length; i++) { li[i].style.zIndex = arr[i].zIndex; startMove(li[i], arr[i]); } } ``` # 代码 全部代码就不贴在博客上了,很简单,没什么可看的,大家可以去我的github上检出 点击进入我的github
## 查看完整DEMO end from {{ site.url }} ================================================ FILE: _posts/2014-9-11-nodeJS-char.md ================================================ --- layout: post title: 基于Node.js + socket.io实现WebSocket的聊天DEMO description: "WebSocket Node.js实现版" tags: [JavaScript, Node.js] image: background: witewall_3.png comments: true share: true --- # 简介 最近看Node.js和HTML5,练手了一个简易版的聊天DEMO,娱乐一下 ## 为什么需要socket.io? node.js提供了高效的服务端运行环境,但是由于浏览器端对HTML5的支持不一, 为了兼容所有浏览器,提供卓越的实时的用户体验,并且为程序员提供客户端与服务端一致的编程体验, 于是socket.io诞生。 简答来说socket.io具体以下特点: 1.socket.io设计的目标是支持任何的浏览器,任何Mobile设备。目前支持主流的PC浏览器 (IE,Safari,Chrome,Firefox,Opera等),Mobile浏览器(iphone Safari/ipad Safari/android WebKit/WebOS WebKit等)。socket.io基于node.js并简化了WebSocket API,统一了通信的API。它支持:WebSocket, Flash Socket, AJAX long-polling, AJAX multipart streaming, Forever IFrame, JSONP polling。 2.socket.io解决了实时的通信问题,并统一了服务端与客户端的编程方式。启动了socket以后,就像建立了一条客户端与服务端的管道,两边可以互通有无。 # 代码 创建app.js 源码如下 ```js var fs = require('fs') //文件操作 , http = require('http') //http服务器 , socketio = require('socket.io'); //socket.io,用来和前台进行交互 var server = http.createServer(function(req, res) { res.writeHead(200, { 'Content-type': 'text/html'}); //将index.html输出 res.end(fs.readFileSync(__dirname + '/index.html')); }).listen(3000, function() { console.log('Listening at: http://localhost:3000'); }); //连接成功的回调 socketio.listen(server).on('connection', function (socket) { socket.on('message', function (msg) { console.log('接受到 ', msg); //将信息发送给其他客户端 socket.broadcast.emit('message', msg); }); }); ``` 创建index.html ```html 控制台: 
``` # 运行&结果 因为依赖了socket.io包,所以用npm 下载 npm install socket.io 最后直接运行 node app.js
home
运行效果
>附上一个实现了很炫聊天DEMO http://segmentfault.com/a/1190000000479518
home
聊天DEMO
end from {{ site.url }} ================================================ FILE: _posts/2014-9-12-css3-lenove.md ================================================ --- layout: post title: JavaScript实现联想校招员工信息展示 description: "使用简单模板+css3+javascript实现CSS3弹窗及切换" tags: [JavaScript, 小练习] image: background: witewall_3.png comments: true share: true --- # 起因 今天和豪哥聊天,才知道他是我老乡,而且特别近。。真的感觉他是我的贵人,这是他从 联想校招扣出来的,我们就用JavaScript来实现吧
home
联想校招员工信息展示
home
点击弹出详细信息
# 过程 给的有HTML还有CSS3,我打算先用前端模板技术讲信息都出来,之前数据是写死在HTML 数据封装在data.js里 ```js var data = []; data.push({ 'name' : 'Dillon', 'enname' : 'Dillon', 'desc' : '来自哈佛的战略伙伴经理', 'pic' : 'images/n8_10.png', 'bigpic' : 'images/j2.jpg' }); data.push({ 'name' : '成俞晟', 'enname' : 'cheng', 'desc' : '技术宅服务器工程师', 'pic' : 'images/n8_07.png', 'bigpic' : 'images/j3.jpg' }); data.push({ 'name' : 'Said', 'enname' : 'Said', 'desc' : '开朗健谈的中国通', 'pic' : 'images/n8_18.png', 'bigpic' : 'images/j5.jpg' }); //... ``` 我们讲以前的HTML写成一个模板 展示区模板 ```html ``` 弹出层模板 ```html

{name}

{enname} {desc} ECS

2013年加入联想,联想游戏中心最霸气的女商务,性格大大咧咧,柔弱的外表下有一颗强大的心。自诩内可安邦定天下,外可御敌千里外。

联想游戏中心作为北研最靓丽的风景线,不仅有着最潮、最流行的游戏可以玩,有各种精美的游戏周边随便拿,最关键的是有这样娇(ba)媚(qi)可(shi)人(zu)的女汉子陪聊,陪工作,陪吃饭。总之一句话,联想游戏中心,你值得拥有。

``` 然后用一下代码弄到HTML里面 ```js //获取LI模板HTML var tempLi = document.getElementById('temp_li').innerHTML; //HTML + 数据最后放到这个数组里 var liArr = []; for (var i = 0; i < data.length; i++) { //替换模板里{}表达式 var itemLi = tempLi.replace(/\{i\}/, i) .replace(/\{name\}/,data[i].name) .replace(/\{desc\}/,data[i].desc) .replace(/\{pic\}/,data[i].pic); liArr.push(itemLi); } //加入到ul里 ul.innerHTML = liArr.join(''); //获取详细信息模板 var tempDialog = dialog.innerHTML; //打开弹出层 function openDialog(dataIndex) { dialog.innerHTML = tempDialog.replace(/\{i\}/,dataIndex) .replace(/\{name\}/,data[dataIndex].name) .replace(/\{enname\}/,data[dataIndex].enname) .replace(/\{desc\}/,data[dataIndex].desc) .replace(/\{bigpic\}/,data[dataIndex].bigpic); bg.className += ' current'; dialog.className += ' current'; } ``` 因为HTML是动态生成的,直接不能加绑定事件,所以使用事件委托 ```js //给每一个li加事件 ul.onclick = function(event) { var e = event || window.event; var target = e.target || e.srcElement; target = getLiByChild(target); if(target) { var curIndex = target.getAttribute('data-id'); openDialog(curIndex); } } //给弹出层加事件 dialog.onclick = function(){ var e = event || window.event; var target = e.target || e.srcElement; var curIndex = +dialog.lastElementChild.getAttribute('data-id'); //点击关闭 if(target.nodeName == 'I' && target.parentNode.className == 'close') { closeDialog(); } //点击上一个 if(target.nodeName == 'I' && target.parentNode.className == 'prev') { var preIndex= curIndex-1; if(preIndex > -1) { closeDialog(function() { openDialog(preIndex); }); }else {//否则循环到最后一个 closeDialog(function() { openDialog(data.length-1); }); } } //点击下一个 if(target.nodeName == 'I' && target.parentNode.className == 'next') { var nextIndex= curIndex+1; if(nextIndex < data.length) { closeDialog(function() { openDialog(nextIndex); }); }else {//否则循环到最后一个 closeDialog(function() { openDialog(0); }); } } } ``` 其中,弹出层里有三个动作; 1.关闭弹窗 2.前一个 2.后一个 我们在里面做处理,如果是前一个为第一个则跳到最后一个,如果为最后一个则跳到前一个 因为我们点击的时候有可能点到SPAN,有可能点到DIV(原因看展示区模板),所以要有个函数来 找到顶层的LI,因为关闭有一个动画效果,我看了CSS是0.3s,所以我们加一个定时器做一个回调 ```js //找到最顶层LI function getLiByChild(element) { var li = element; while(li.nodeName != 'LI') { li = li.parentNode; } return li; } //关闭Dialog function closeDialog(func) { bg.className = 'box_bg1'; dialog.className = 'box_ovo'; setTimeout(function(){ if(dialog.className == 'box_ovo') { func && func(); } },300); } ``` # 获取代码
代码就不贴在博客上了,很简单,没什么可看的,大家可以去我的github上检出 ## 查看完整DEMO end from{{ site.url }} ================================================ FILE: _posts/2014-9-18-no-sleep.md ================================================ --- layout: post title: 夜未眠 description: "找工作这几天的心声" tags: [心声] image: background: witewall_3.png comments: true share: true ---
home
图书一角
>小时候总觉得「努力」是没有「天赋」的人做的事情;长大了才明白,原来「努力」才是最珍贵的「天赋」。 AL最后HR面试的时候我说了上面的话。 自己以前走的一些弯路,高考那一关很艰难。那一次我终于明白,人, 不在合适的时候做合适的事情,是要倒霉的。今天和悦在聊天,她说很多时候,她想干点事情,但又无从下手。 来到这个学校,最让我感激的还是我们创新实验室。因为我想干点事情,而这块地方就是我挥洒热血的地方。 感谢实验室的环境与自己的努力,让我从容的拿到了offer。天时地利人和都有了,我抓住了这次机会。 晚上的时光过的是很快的。每当我累了的时候,就听见昌志的话在耳边响起:“每天7点起来读英语,8点上班, 加班到晚上10点,还得回来啃书到2点,累成狗”。压力不是有人比你努力,而是比你牛叉几倍的人依然比你努力。 你只看到那些光鲜的大牛,但并没有看到大牛在人背后默默的啃书,读代码。这次拿到offer并不是结束,而是才上路。 想想那些比你牛的人在干什么,或许你会关掉正在打开的游戏。 感激高考那一关带来的无助于迷茫,感激那些冷嘲热讽的话语。正是这些,推动我越过高山。 咬牙坚持走过,回头看看,那只是一个插曲。人生并没有难以跨越的鸿沟.经历是一笔无形的财富, 我在享受成长过程带来的收获的同时,在慢慢走向自己最纯真的梦想。 我相信,这个梦,总会实现。 end from {{ site.url }} ================================================ FILE: _posts/2014-9-2-what-happens-when-you-type-in-a-url-in-browser.md ================================================ --- layout: post title: 从输入 URL 到页面加载完成的过程中都发生了什么事情? description: "what happens when you type in a url in browser,对stackoverflow回答的扩展" tags: [浏览器, 计算机网络] image: background: witewall_3.png comments: true share: true --- 之前阿里有一个笔试题:在在浏览器输入url发生了什么事请,看到Stack Overflow有一个很好的资料, 记录了一下,以后做个参考. 1.browser checks cache; if requested object is in cache and is fresh, skip to #9 2.browser asks OS for server's IP address 3.OS makes a DNS lookup and replies the IP address to the browser 4.browser opens a TCP connection to server (this step is much more complex with HTTPS) 5.browser sends the HTTP request through TCP connection 6.browser receives HTTP response and may close the TCP connection, or reuse it for another request 7.browser checks if the response is a redirect (3xx result status codes), authorization request (401), error (4xx and 5xx), etc.; these are handled differently from normal responses (2xx) 8.if cacheable, response is stored in cache 9.browser decodes response (e.g. if it's gzipped) 10.browser determines what to do with response (e.g. is it a HTML page, is it an image, is it a sound clip?) 11.browser renders response, or offers a download dialog for unrecognized types 1.检查浏览器缓存,如果你请求的对象依据缓存下来了,则跳到第9步 2.浏览器会询问操作系统你请求的服务器的IP 3.操作系统先查询本地Host文件;如果hosts里没有这个域名的映射,则查找本地DNS解析器缓存; 如果还是没有,会找TCP/ip参数中设置的首选DNS服务器,查询本地区域文件与缓存; 如果本地DNS服务器本地区域文件与缓存解析都失效,则根据本地DNS服务器的设置进行查询, 如果未用转发模式,本地DNS就把请求发至13台根DNS, 如果开启转发模式,此DNS服务器就会把请求转发至上一级DNS服务器,由上一级服务器进行解析, 上一级服务器如果不能解析,或找根DNS或把转请求转至上上级,以此循环。最后返回IP给浏览器 4.浏览器拿到IP后,想会向服务器建立一个socket连接(不考虑https) 5.浏览器通过TCP向服务器发送HTTP请求的 6.浏览器接收到服务器响应就会断开TCP连接,或者为了其他请求重用它 7.浏览器检查响应的状态是重定向(3xx)、要求授权(401)、服务器错误(4xx 和 5xx),如果是正常则会返回2xx(200), 8.如果是可缓存的,响应则缓存在内存里 9.浏览器将解码响应(不考虑gzip压缩) 10.浏览器决定如何响应,例如图片、HTML、媒体文件 11.浏览器将渲染请求,或者弹出一个下载对话框 大家也可以参考下面补充文章:
浏览器是怎样工作的(一):基础知识
浏览器是怎样工作的:渲染引擎,HTML解析(连载二)
开源中国HTTP状态码常用对照表 end ================================================ FILE: _posts/2014-9-26-div-center.md ================================================ --- layout: post title: CSS居中完全解决方案 description: "各种情景下的CSS水平垂直居中" tags: [CSS] image: background: witewall_3.png comments: true share: true --- >上次面试面试官问到了,问了个定宽局中和不定宽局中,下来我把所有有关CSS居中都总结了一下 # 水平居中 ## 行内元素 把行内元素嵌套在一个DIV中,并且在DIV中设置以下样式 ```css a{ text-align: center; } ``` ## 块级元素 对于定宽的块级元素,我们要设置起margin-top,margin-right 为auto ```css .center{ margin: 0 auto; } ``` ## 多个块级元素(inline-block) 多个块级元素,我们将其display设置为inline-block;然后将父级元素设置一下属性 ```css div{ text-align: center; } ``` ## 多个块级元素(flex) 设置需要水平居中的块状元素的父元素display为flex ,并且justify-content属性为center即可 ```css body{ display: flex; justify-content: center; } ``` # 垂直居中 ## 单行 行内元素 将行内元素的height和line-height设置为一致即可 ```css a{ height: 200px; line-height:200px; } ``` ## 多行 行内元素 如果行内元素文字过多产生多行,则在父级元素设置display: table-cell;vertical-align:middle; ```css .container{ width: 300px; height: 300px; display: table-cell; vertical-align:middle; } ``` ## 已知高度的块级元素 将块级元素设置绝对定位,top为50%,margin-top:-height/2 ```css div{ height: 100px; position: absolute; top: 50%; margin-top: -50px; padding:0; } ``` ## 未知高度的块级元素 使用CSS translate,将块级元素设置绝对定位,top为50%,transform: translateY(-50%); ```css div{ position: absolute; top: 50%; -webkit-transform:translateY(-50%); -moz-transform:translateY(-50%); transform: translateY(-50%); padding:0; } ``` # 水平垂直居中 ## 已知高度、宽度的元素 将块级元素设置绝对定位,top为50%,left:50%;margin-top:-height/2;margin-left:-width/2 ```css div{ width: 150px; height: 150px; position: absolute; top: 50%; left: 50%; margin-top: -75px; margin-left: -75px; } ``` ## 已知高度、宽度的元素(flex) 给父级使用flex布局 ```css div{ display: flex; justify-content:center; align-items: center; } ``` ## 未知高度、宽度的元素 使用CSS translate ```css div{ position:absolute; top:50%; left:50%; -webkit-transform:translate(-50%,-50%); -moz-transform:translate(-50%,-50%); transform:translate(-50%,-50%); } ``` end from{{ site.url }} ================================================ FILE: _posts/2014-9-26-think-in-seo.md ================================================ --- layout: post title: 和我一起来了解SEO description: "前端工程师必备的SEO知识" tags: [SEO] image: background: witewall_3.png comments: true share: true --- # 基础知识 ## 搜索引擎 搜索引擎爬虫会检索各个网站,分析他们的关键字,从一个连接到另一个连接,如果爬虫觉得这个关键字是有用的 就会存入搜索引擎数据库,反之如果没用的、恶意的、或者已经在数据库的,就会舍弃。搜索引擎数据库 保证是爬虫爬过的最新的数据。用户在使用搜索引擎会在搜索引擎数据库查找关键词,展现给用的的是排序后的结果。除开 某些搜索引擎推广的,剩余的一般是按照关联度来排序。 ## SEO简介 SEO(Search Engine Optimization)汉译为搜索引擎优化.seo优化是专门利用搜索引擎的搜索规则来提高目 前网站在有关搜索引擎内的自然排名的方式.SEO的目的理解是为网站提供生态式的自我营销解决方案,让网站在 行业内占据领先地位,从而获得品牌收益. 如在百度搜索hacke2,第一个就是我的前端博客www.hacke2.cn
home
百度搜索hacke2
### 白帽SEO 采用SEO的思维,合理优化网站,提高用户体验,合理与其他网站互联。从而使站点在搜索引擎排名提升。 白帽SEO关注的是长远利益,需要的时间长,但效果稳定。 ### 黑帽SEO 就是采用搜索引擎禁止的方式优化网站,影响搜索引擎对网站排名的合理和公正性。同时随时因为搜索引擎 算法的改变而面临惩罚。比如加的关键字与自己网站根本无任何关系,这些关键字一般都是最近最火的关键字 欺骗用户、欺骗爬虫。 ### 白帽SEO的相关手段 * 网站标题、关键字、描述 * 网站内容优化 * robot.txt * 网站地图 * 增加外链引用 一般有关前端工程师的有:网站布局结构优化、网页代码优化。 # 前端SEO ## 网站布局结构优化 网站结构尽量简单、清晰。推荐扁平化结构。相关手段如下: ### 控制首页连接数量 对于中小型网站来说,最好不要太多,但也不能太少 ### 扁平化目录层次 爬虫希望看到你网站的结果是树形结构。 如动物-->猫科动物-->狮子 ### 导航 导航尽量是文字,而且层级尽量小于三级。如本站导航若为图片,title和alt必须添加
home
本站导航
### 其他 使用面包屑导航、单个页面不超过100k ## 代码 ### head title 表示网页的标题 description 表示网页的描述 keywords 表示网页的关键字 下面是本站的相关描述,右键查看源代码即可看到 ```html hacke2's blog | WEB前端,一路前行 – hacke2's blog ``` ### 语义化 HTML 5的革新——语义化标签 深更半夜话(html)语义 ### 代码优化 SEO网页代码优化 优化HTML代码加快网页速度 >部分参考百度百科、慕课网 end from {{ site.url }} ================================================ FILE: _posts/2014-9-28-gokk.md ================================================ --- layout: post title: 基于Node.js + jade + Mongoose 模仿gokk.tv description: "向goxiazai致敬" tags: [Node.js] image: background: witewall_3.png comments: true share: true --- # 关于gokk 大学的娱乐活动基本就是在寝室看电影了→_→,一般都会选择去goxiazai.cc上看,里面的资源多,质量高 。站长会推荐评分很高广受好评的电影给大家免费下载,整体来说真是不错,但前两月由于版权问题被迫转型 这也是没办法的事,程序员更应该尊重版权问题,我们也能理解,后来站长又开了gokk个不是给地址让你 下载,而是将网络一些优秀视频站点资源提供出来观看,质量变低了,好怀念以前的goxiazai啊。。 最近在学习Node.js,也看了imooc的源码 ,自己模仿gokk.tv的前台搭建了一个基于Node.js + Mongoose + Bootstrap,很简单的页面, 花了两天时间,第一次Node体验就交给gokk了,向goxiazai致敬! # 展示
home
首页一
home
首页二
home
详情页
home
新增页
# 获取源码 源码托管于git 点击获取 # 部署于运行 使用npm将项目打包,大家检出来后使用npm install即可安装 使用node app运行,数据库使用Mongdb,请自行安装,测试数据在test/db.js下 希望您能提出建议或意见 # 数据结构 movies 下面有columnName 表示栏目的名称,如电影、动画、娱乐等 movie是一个数组 里面放入Json对象,表示改栏目下的所有电影,这样非关系数据库的好处就是之前如果遵循低冗余 原则必须建立两张表来使column和movie一对多,后来感觉应该用两个Schema更合理 # 功能 首页、详情页、新增 未实现:分页、查看列表、后台删除、编辑 # 什么原因让我只写了个新增功能? 由于是第一次编写node,在编写的过程中遇到很多问题,虽然node提供了分页limit,但是 只能分一张表内的,比如movies这张表,不能对一条数据低下movie数组集合下分量取出。 后来在Mongoose学习参考文档——基础篇 看到有这么一个东西Sub Docs >如同SQL数据库中2张表有主外关系,Mongoose将2个Document的嵌套叫做Sub-Docs(子文档)简单的说就是一个Document嵌套另外一个Document或者Documents: 这可能是我想要的,但是第一次开发重点不是在Mongoose上,node的书还不是看的很多,这算是我的 一个学习过程中的小测试吧!计划开发两天时间,如果修改会越陷越深,我明白我现在的node开发流程有很大问题,流程不不正确 。我把在这上面越陷越深,写起来感觉效率真的不是很高,所以其他功能立即停止开发。 这也是项目半成品的一个原因吧 end from {{ site.url }} ================================================ FILE: _posts/2014-9-30-nodeJS-sublime-3.md ================================================ --- layout: post title: 在Sublime Text3 开发Node.js遇到的一个小问题 description: "主要解决了重新运行端口占用的情况" tags: [Node.js] image: background: witewall_3.png comments: true share: true --- 以前的Sublime Text 2包管理出现问题了,不能安装新包,让人开发很捉急,今天装了个3,这个问题解决了 那我们就一起用Sublime Text 3 来玩Node.js吧! cn node说的很清楚,在这里就不细表 http://cnodejs.org/topic/51ee453af4963ade0ebde85e 这里说说他们说的一个问题
home
重新运行后端口占用
我也是在这边搞了半天,最后用下面方法解决的 ```js { //这里加了一句关闭 "cmd": ["taskkill /F /IM node.exe", ""], "cmd": ["node", "$file"] } ```
home
重新运行后端口占用
但也出现一个问题,如果你的app.js正在运行,而你在这边调试单元测试,这样会把主程序也会关掉,这个东西 不能常用,还是使用Grunt最为妥当 完整Nodejs.sublime-build ```js { "cmd": ["node", "$file"], "file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)", "selector": "source.js", "shell":true, "encoding": "utf-8", "windows": { "cmd": ["taskkill /F /IM node.exe", ""], "cmd": ["node", "$file"] }, "linux": { "cmd": ["killall node; node", "$file"] } } ``` end from {{ site.url }} ================================================ FILE: _posts/2014-9-4-javascript-seamless-handover.md ================================================ --- layout: post title: JavaScript打造无缝切换 description: "用JavaScrip写的无缝切换,弥补一年前的遗憾" tags: [JavaScript, 小练习] image: background: witewall_3.png comments: true share: true --- # 起因 一年前写管理学院的时候,那时候做首页有一个幻灯片,由于之前没交流好,CL写的静态页面幻灯片图片是在背景里,让我用jq写, 当时就感觉特别啃爹,图片写在了css里。。好坑爹,自己又懒得改,只能硬着头皮用jq写,一堆临时变量,如num++,各种奇葩,最后加了 个jq淡隐淡出的效果,就交差了,,代码如下: 这几天一个前端qq群主分享了一个东西说平时可以练练手,我没有做,可是看到他说,群里面有些人小东西不屑做,稍微大点有不会做 我当时就感觉说我。。刚才写了一下,与大家分享 # 思路 关键一点就是克隆,而且是深克隆,obj.clone(true);这样就能克服该节点的所有子节点。之所以选择克隆,是因为直接删除太突兀了。 一般无缝切换的幻灯片是这样做的 1.点击前一个:将最开始的节点克隆到最后一个节点后面,然后整体向前移,并且删除掉第一个元素 2.点击后一个:将最后节点克隆到最前面的节点钱,然后整体向后移,并且删除掉最后一个元素 动画效果用的智能社的动画脚本 # 代码 代码在github上,有兴趣的看下: JavaScript打造无缝切换
## 查看完整DEMO end ================================================ FILE: _posts/2014-9-4-my-show-4-angularjs.md ================================================ --- layout: post title: AngularJs+BootStarp+fontAwesome打造个人展示页面 description: "简单的个人展示的界面" tags: [JavaScript, AngularJs, BootStarp] image: background: witewall_3.png comments: true share: true --- # 起因 实验室马上要出去找工作了,大家想写一个展示自己项目的东西,没必要做的太复杂,我就把这几天学到的结合了一下,算是做了 一个大概的样子出来,大家可以自己放里面加内容
home
点击右上角项目名称可切换不同视图
# 所含的技术 ## AngularJs 里面MVC使用AngularJs来实现,主要用了其中两个技术: 1.模板技术 2.路由技术 之所以用了AngularJs的路由技术,是因为做一个页面的跳转,之前做开发都是以sevlet或者JAVA MVC框架来做,从后台取数据放到前台, 通过控制器来觉得给用户返回怎样的一个视图,因为这次没必要做的比较复杂,使用AngularJs上两种技术完全能达到我们的要求,而且还是 无刷新的。 ## BootStarp 优秀的UI框架,大家做简历或者项目展示的时候可能会想要炫酷的效果:) ## fontAwesome 一款优秀的图标字体库 # 代码 代码就不贴在博客上了,很简单,没什么可看的,大家可以去我的github上检出 点击进入github: AngularJs+BootStarp+fontAwesome打造个人展示页面
## 查看完整DEMO end from{{ site.url }} ================================================ FILE: _posts/2015-1-13-what-you-need-to-know-about-block-scope-let.md ================================================ --- layout: post title: 译-你需要知道的块级作用域 - let description: "JavaScript 未来声明变量的方式" tags: [翻译] image: background: witewall_3.png comments: true share: true --- >http://es6rocks.com/2014/08/what-you-need-to-know-about-block-scope-let/ 原文链接 变量声明在任何语言中都是非常基础的东西,理解变量在作用域下如何工作是非常重要的事情。 在大多数语言中,如`Python`,他有两个作用域:局部 和 全局。如下,变量定义在代码开头部分则为全局变量,在函数里面声明变量则为局部变量。 JavaScript也很相似,看以下例子: ```python # Python x = 1 # 全局变量 y = 3 def sum(a, b): s = a + b # 局部变量 return s ``` ```js // JavaScript var x = 1; // 全局变量 var y = 3; function sum(a, b) { var s = a + b; // 局部变量 return s; } ``` 以C语言为基础而衍生出来的语言(JavaScript, PHP)一般是块级作用域,但JavaScript却不是,当你在一个函数体内声明一个变量,他会在父级或者全局来寻找这个变量,这种行为叫做`变量提升`,与其他语言不同,JavaScript会在`for循环`外面使用这些变量。 看下面这个例子 ```js // JavaScript for (var i = 0; i <= 2; i += 1) { console.log(i); // current i } console.log(i); // last i ``` 在这个例子中,i竟然可以在循环外面被访问到!这在其他语言是不可思议的!这是很常见的问题,但JS程序员不见得都会关注它。 ## let 声明 let在ES6中出现是为了`代替`var。是的,我们的想法是在未来停用`var`,现在就停止时不切实际的,因为有很多网站都还在用它。 使用`let`会像和其他语言大道预期的效果 例子: ```js let foo = true; if (foo) { let bar = 'baz'; console.log(bar); // outputs 'baz' } try { console.log(bar); } catch (e) { console.log("bar doesn't exist"); } ``` 正如你想看到的,`let`会解决在for循环变量的问题。 ## 现在的支持 你现在就可以使用let 查看 Kangax 的ES6 支持表格 > http://kangax.github.io/compat-table/es6/#. let已经在z最新的现代浏览器有所支持( >= IE 11)和[Traceur](https://github.com/google/traceur-compiler)编译器 你可以在火狐开发者工具中试试 ![es6 let](http://es6rocks.com/img/let.gif) 注: Michał Gołębiowski 指出在各个浏览器的规范不一样,使用时可能会出现bug。在当前还未正式流行开,这种情况可能会持续到2015年中旬。 ## 结论 声明变量虽然简单,但在JavaScript语言中会让初学者头痛的。 `let`声明变量更直观,符合基于c的语言. var应该停止使用,只有这样,没有变量提示的`let`才能正在运用到未来。 **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2015-1-14-2015-plan.md ================================================ --- layout: post title: 该是做做计划的时候了! description: "2015年计划" tags: [翻译] image: background: witewall_3.png comments: true share: true --- >看着大神们一个个做着年终总结,小菜页凑个热闹,做做2015年的计划 `2014年`就这样过去了,回忆过去,痛苦的相思忘不了。2014年肉果很多,尤为重要的一点:自己走上了前端这一道路。 加了高质量的前端群,认识了很多行内人士,微博关注了很多人,开始玩GitHub了,放弃了在CSDN上的博客,开通了专门前端的Github Page,写了一个作品页,参加了校招面试,拿到了阿里巴巴集团的Offer,现在有做起了移动端WEB编程,前景很大...收获实在是太多了。 在新的一年里,希望能在前端路上有所建树,找到一个重心点深挖,最好通过公司的实际业务需求,学到一下几方面的知识: ## 技术 1.ES6 之前和[ES6 Rocks](http://www.es6rocks.com)联系,希望能创建一个中国版的ES6 Rocks,翻译了几篇文章后,鸟无音讯了,试着再联系联系 2.HTML5和CSS3 借着公司移动端WEB的春风,相信今年在H5上面也会有所建树 3.backbone,ng等MVC框架 还没深入研究过,得好好理解前端MVC在大型WEB应用上的代码组织 4.属于自己的前端框架 一个小心愿,在此期间也会读读其他框架的源码 5.算法 小虫的群里建了一个项目:[每日一题](https://github.com/nunnly/everycode/issues),上面有很多算法上的东西,这一块是我的短板 6.设计 看到那么多人设计的东西,感觉看起来整个人都变好了,每一个毛孔都感觉特别放松,很美。希望能在这方面也入个门 7.读书 在公司感觉整个人都很忙,在北京完全没有看过一本书了,希望能忙里偷闲,看几本书,让自己也智慧点, ## 学业 大学快结束了,在学业上今年最重要的也就是毕业设计,我的选题是《维斯顿智能家居集群版的设计与实现》希望能顺顺利利的通过。 ## 健康 看着越来越大的肚子,我在想我怎么年纪轻轻身材就已经像四五十岁的人了?在公司和小伙伴经常一起打乒乓球,玩桌上足球,但我的肚子,还是得有个计划收一收。 ## 独立能力&自理能力 以前衣服什么的都是母亲帮我洗,现在在外面,靠不了别人,还是得锻炼自己的独立能力和自理能力。(写这篇博文前刚刚洗了堆积已久的袜子--) 其实最重要的还是计划的执行力和自制力,希望能在2015年有一个质的飞跃! **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2015-10-21-scroll-in-uc.md ================================================ --- layout: post title: 在移动端上使用原生滑屏解决方案 description: "解决UC左右滑动BUG" tags: [Mobile Web] image: background: witewall_3.png comments: true share: true --- >最近有个需求,就是非常简单地横向滑动。打算使用overflow-x:auto;来试验原生滑动,但在安卓版UC下当滑到最左或者最右会`默认启用UC上一页、下一页手势`,导致页面的跳转(原生浏览器无此手势)。之前好几个项目之前视频有使用js处理(query: 夏洛特烦恼), 横向滑动没有加惯性支持,非常卡顿。感觉此类对距离没有要求的滑动用原生最好,而且overflow-x:auto;兼容性支持到安卓2.1以上,使用起来也非常方便,所以打算在UC下做一下适配。 ## 使用css布局 先使用overflow-x:auto;给UL加上一下css,做出横向滑动的效果: ```css overflow-x: auto; white-space: nowrap; -webkit-overflow-scrolling: touch; ``` 这样,一个简单地滚动效果就实现了。如图 ![css滑动](http://ww4.sinaimg.cn/large/8ae515a4gw1ex7vnx0qpnj20ke066gn4.jpg) 在UC浏览器安卓版下试验,不出所料的滑到最右边启用了UC上一页、下一页手势。 ## 解决思路 经过无数次和@绍伟的试验,给`body`绑定`touchmove`事件,并且组织默认行为就能把UC效果干掉,那么就有思路了: 1. 在滑到最左边或者最右边给Body绑定事件,组织默认行为 2. 当手指抬起解绑事件 ## 参考代码 具体参考代码如下: ```js var preventUCDefault = (function() { var ua = window.navigator.userAgent, startX = 0, diffY = 0, bindPreventTouch = function() { $(document.body).on('touchmove.prevUC', function(e) { e.preventDefault(); }); }, isAndroid = (function() { //https://github.com/amfe/lib-env/blob/master/src/browser.js#L70 return (!!ua.match(/(?:UCWEB|UCBrowser\/)([\d\.]+)/) && !!ua.match(/Android[\s\/]([\d\.]+)/)); })(); return { init : function(ul) { if(isAndroid) { var scrollWidth = ul[0].scrollWidth; ul.on('touchstart.prevUC', function(e) { startX = e.touches[0].pageX; }); ul.on('touchmove.prevUC',function(e) { diffY = e.touches[0].pageX - startX; if($(this).scrollLeft() == 0 && diffY > 0) { //到最左 bindPreventTouch(); }else if((scrollWidth - $(this).scrollLeft() - ul.width()) === 0 && diffY < 0) { //到最右 bindPreventTouch(); } }); ul.on('touchend.prevUC',function(e) { $(document.body).off('touchmove.prevUC'); }); } } } })(); preventUCDefault.init(scope.find('.slide-image ul')); ``` 经过QA测试,低版本UC下滑动效果也很不错呢! ## 优化 当然,想开启gpu加速可以加上下句话: ```css -webkit-transform:translateZ(0); ``` 另外,使用原生滑动会出现滚动条,如果想达到极致体验的话,@靳磊给了两个思路: 1. 外面套一层,给定高度+overflow:hidden; 2. 加一个伪元素将滚动条遮起来 使用伪元素代码如下 ```css ul::after { display: block; content: ""; position: absolute; z-index: 10; width: 100%; background-color: #fff; height: 10px; margin-top : -11px; } ``` ## 总结 对于一个问题一个人思考思维会很局限,和大家一起讨论完成学到了很多解决的办法,能将任务完成最优而且增进团队的融合性。 --- 更新 2015年12月14日 隐藏滚动条还有更好地方法 ```css ul::-webkit-scrollbar { display: none; } ``` 主要解决背景非纯色而是虚化这样的需求,uc下有效,但是safari下还是会出现滚动条,有点小遗憾。 **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2015-10-9-javascript-iterables-and-iterators.md ================================================ --- layout: post title: 译-Javascript中的Iterables和Iterators description: "ES6迭代器介绍" tags: [Javascript, ES6] image: background: witewall_3.png comments: true share: true --- >迭代器是es6引入的重要概念(Java中很早就有了),也是理解generator的基础。英文原文:http://jsrocks.org/2015/09/javascript-iterables-and-iterators/ ECMAScript 2015 (ES6) 介绍了两个新的概念,它们密切相关: **iterables** and **iterators**.
希望你在阅读了这篇文章后,会了解到这两个概念的重要性,并且能在日常开发中使用它们。 ## Iterables 可遍历对象:是指实现了[Iterable interface](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-iterable-interface)接口的对象. 可遍历对象会暴露一个遍历方法,我们可以通过这个暴露的方法去自定义遍历行为。 请看下面的demo: ```js //推荐google浏览器,执行报错的话,说明你的浏览器不支持这两个新的特征,我用的是google chrome v45.0,顺利执行 let iterable = [1, 2, 3]; for (let item of iterable) { console.log(item); // 1, 2, 3 } let iterable2 = new Set([4, 5, 6]); for (let item of iterable2) { console.log(item); // 4, 5, 6 } let iterable3 = '789'; for (let item of iterable3) { console.log(item); // '7', '8', '9' } let notIterable = {name:'alibaba'}; for(let item of notIterable){ console.log(item) // 执行报错!原因很简单,普通Oject并不是可遍历对象 } ``` [for-of](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/for...of) 语法支持可遍历对象, 因此我们可以这种规范的遍历语法去遍历实现了Iterable接口的对象。 ### 那Iterable接口到底是个什么东东? 这里的Iterable其实是指一个`[Symbol.iterator]`方法,任何对象只要包含了这个`[Symbol.iterator]`方法,它就是可遍历对象。
如果你不太了解这个ES6新特征Symbol你可以看下这篇文章:[阮一峰:es6 symbols](http://es6.ruanyifeng.com/#docs/symbol)(你也可以看英文文章:[symbols](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Symbol))。
`Symbol.iterator` 是个一个 [*well-known*](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Symbol#Well-known_symbols) (内置于语言内部) Symbol, 他主要用于定义可遍历对象。 在刚才的例子中,我们其实隐式的使用了`[Symbol.iterator]`方法,在Array,Set,String的原型prototype里都包含了`[Symbol.iterator]`方法。比如 [Array](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array), [Set](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Set), [String](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String) and [Map](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map) 它们都定义了默认的遍历行为, 然而[Object](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object) 并没这个方法。 Iterable接口允许自定义遍历行为,请看下面的例子,我们讲对象的遍历行为设置为数组的遍历行为,让对象能像数组那样遍历: ```js let iterable = { 0: 'a', 1: 'b', 2: 'c', length: 3, [Symbol.iterator]: Array.prototype[Symbol.iterator] }; for (let item of iterable) { console.log(item); // 'a', 'b', 'c' } //这个还是比较实用的,毕竟在es5中Oject遍历用for in不是很简洁。 ``` 现在你可能会问:**"怎样才能自定义遍历行为?"**
我们已经知道:添加一个`[Symbol.iterator]`可以让一个对象变为可遍历的,但是需要注意一点的是:`[Symbol.iterator]`方法必须返回一个 *iterator object* ,就是这个iterator object负责完成遍历逻辑。不要急,在下一个部分我们介绍它的。 在介绍iterators(迭代器)之前,让我们先来回顾下Iterable interface的概念: - 它意味着可以给使用者一个可遍历的对象(iterable object: "嗨,我有一个`[Symbol.iterator]` 方法."); - 它会提供一个标准的方法去遍历任何可遍历的对象(iterable object: "亲,你可以调用`[Symbol.iterator]`方法去完成遍历过程"). ## Iterators 迭代器对象是指实现了[Iterator interface](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-iterator-interface)的对象,迭代器对象必须拥有一个`next`方法,并且这个方法的返回值必须要是 `{ value: Any, done: Boolean }`这种格式的对象,当我们第一次调用next方法时,next方法会返回第一个遍历结果元素。这个`done`属性是指当前遍历的元素是否是最后一个元素,如果done:true意味着所有遍历完成。 下面例子介绍如何使用遍历器去遍历数组: ```js let iterable = ['a', 'b', 'c']; // 获取遍历器 let iterator = iterable[Symbol.iterator](); //使用next方法遍历元素(好像java遍历器) iterator.next(); // { value: 'a', done: false } iterator.next(); // { value: 'b', done: false } iterator.next(); // { value: 'c', done: false } iterator.next(); // { value: undefined, done: true } ``` 最开始的例子我们不是使用for-of的语法么!现在我们来看一下for-of语法的内部实现方式: ```js let iterable = ['a', 'b', 'c']; // 简便语法,利用for-of for (let item of iterable) { console.log(item); // 'a', 'b', 'c' } // for-of的内部实现方式,是不是感觉很简单!真的好像java遍历器 for (let _iterator = iterable[Symbol.iterator](), _result, item; _result = _iterator.next(), item = _result.value, !_result.done;) { console.log(item); // 'a', 'b', 'c' } ``` 现在我们知道了iterables和iterators后,现在我们可以创建自定义遍历行为的可遍历对象了。首先,我们先来自己实现一个类似数组遍历行为的遍历器: ```js let iterable = { 0: 'a', 1: 'b', 2: 'c', length: 3, [Symbol.iterator]() { let index = 0; return { next: () => { let value = this[index]; let done = index === this.length; index++; return { value, done }; } }; } }; //使用for-of语法完成遍历 for (let item of iterable) { console.log(item); // 'a', 'b', 'c' } ``` 额,这个看起来是有点复杂,不清晰,但是不要慌,让我们仔细的分析下它。 我们一开始用JavaScript本文化特征语法创建了一个对象。并且用 [computed properties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Computed_property_names) 和 [shorthand methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions) ES2015 本文化扩展语法给这个对象定义了一个`[Symbol.iterator]`方法,这个对象有了`[Symbol.iterator]`方法,因此他也成了一个可遍历对象。 这个`[Symbol.iterator]`方法实现了默认的遍历行为,这个方法返回了一个遍历器对象,这个遍历器对象包含了上文我们说的next方法,所以我们才能把`[Symbol.iterator]`方法返回的对象视为遍历器对象。 其中`next`方法里面用到了[arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions),所以next方法里的this才会指向iterable对象。(个人来说,我认为ES6 中箭头函数是最强大,最实用,最能简化现有js开发的新特征之一,个人看法,不喜勿喷)。 next方法返回了一个由value和done属性组成的对象,语法很简洁,很实用,用的是[shorthand properties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Property_definitions)语法,简单来说{value,done}就等于{'value':value,'done':done}。 看到这里,相信你已经完全上面例子里每行代码是怎么工作的了。`for-of`语法会调用 `[Symbol.iterator]`方法来获取遍历器对象,遍历对象里有一个next方法,通过调用next方法遍历元素。 ### 有必要把一个遍历接口设计得这么复杂? 恩,总之是需要的。下面我讲列出最常见的问题和疑惑,并以QA的方式展现给大家。 ### Q. 为什么是`[Symbol.iterator]`属性是一个方法,这个方法会返回一个遍历器,而不是这个属性直接就是一个遍历器? 原因是:如果直接是一个遍历器,当你同时想多次遍历时,就不能实现了,同时多次遍历这个可能有点难理解,请看下面的代码: ```js let iterable = [1, 2, 3, 4]; let iterator1 = iterable[Symbol.iterator](); let iterator2 = iterable[Symbol.iterator](); iterator1.next(); // { value: 1, done: false } iterator2.next(); // { value: 1, done: false } iterator2.next(); // { value: 2, done: false } iterator2.next(); // { value: 3, done: false } iterator1.next(); // { value: 2, done: false } //可以看到iterator1和iterator2是互不影响的 ``` 这个例子很勉强,重复遍历相同的数据事时上不太常见。但是异步处理迭代之间的每个值,这在[Koa](http://koajs.com/)和[co](https://github.com/tj/co)中就很好地表现了这一点。尽管它们是利用生成器函数返回的遍历器。 ### Q. 为什么遍历器的`next`方法返回一个新的数据结构,而不是直接返回遍历的元素值呢? 在最初的遍历器设计中,这个`next`方法只会返回元素值。那么问题就来了:我怎么知道遍历器已经完成所有的遍历行为呢? 如果让`next`函数抛出一个错误来标明遍历已完成这样又太土了!因为这样你在调用`next`函数时,就必须给它包一层`try/catch`。所以才会让`next`函数返回的数据里有一个`done`属性。 ### Q. 为什么遍历器最后一个遍历结果让人感觉有点冗余? 请看下面的代码: ```js let iterable = [1, 2]; let it = iterable[Symbol.iterator](); it.next() // { value: "a", done: false } it.next() // { value: "b", done: false } //明明只有2个元素,为毛非要遍历三次,才算遍历完成,蛋疼????!!!! it.next() // { value: undefined, done: true } ``` 当`next`方法返回的结果`done`属性为`true`时,遍历才算完成,并且`value`属性的值为`undefined`。遍历器让value和`down:true`的对象为返回值,是为了让这个`down:true`的对象作为一个遍历完成的标志,并不是一个遍历的元素。例如在`for-of`,`Array.from`的内部实现里,它们都会忽略掉这个返回值。 ### Q. 我能让遍历器的`next`方法接收参数么? 是的,这样是可以的。请看下面的例子: ```js let echoIterator = { next(value) { return { value, done: false }; } }; echoIterator.next(42); // { value: 42, done: false } ``` 正如上面的例子一样,你在自定义的遍历器里,是可以给next方法配置参数的!但是这种遍历器如果使用`for-of`或`Array.from`这些语法时,就会发生异常,因为`for-of`在调用你的next方法时并不会传递参数给next方法。所以请慎用这种方式。 ### Q. 是否可以无限的调用遍历器的遍历方法? 是的,你可以无限的调用遍历器的`next`方法,即使遍历行为已经完成。只是返回的结果都是value=undefined,down=true的对象。 ## 可遍历的遍历器(Iterable iterators) 先来一个快速的知识回顾: - 可变遍历接口要求实现一个`[Symbol.iterator]`方法; - 遍历器接口需要实现一个`next`方法; 请打开你的脑洞:要是我们让一个对象既有`[Symbol.iterator]`方法,既有`next`方法,那这个对象岂不是成了一个可遍历的遍历器了。 实际上,大多数遍历器都实现了可遍历接口,都有`[Symbol.iterator]`方法,但是请记住:遍历器的`[Symbol.iterator]`方法通常会返回遍历器本身,而不是一个新的遍历器对象。 请看下面的例子: ```js let iterable = [1, 2]; let iterator = iterable[Symbol.iterator](); var iterator1 = iterable[Symbol.iterator](); var iterator2 = iterable[Symbol.iterator](); iterator1 == iterator2 // false //为毛iterator1又不等于iterator2了? 亲,iterator1和iterator2是分别由Iterable Oject的`[Symbol.iterator]`方法生成的对象,它们当然不相等了。 var iterator3 = iterator[Symbol.iterator](); var iterator4 = iterator[Symbol.iterator](); iterator == iterator3 == iterator4 // true //iterator3和iterator4都是指向的是iterator,iterator和iterator3和iterator4其实是一个对象所以它们是相等的。 ``` 这是一个简单的可遍历的遍历器: ```js let iterableIterator = { next() {/*...*/}, [Symbol.iterator]() { return this; } }; ``` 现在你可能会问:**这样有什么用啊? 然并卵?** 好的,现在请思考一个问题: **一个数据源可能需要有多次遍历行为.**
请记住:`for-of`语法的内部实现里,它会重复调用iterable对象的`[Symbol.iterator]`方法来获取iterator.(我这这里其实有一个疑问:为毛for-of的内部实现里,会一直不断的获取iterator,既然iterator的`[Symbol.iterator]`方法方法会返回iterator自身,那还需调用这个方法来获取iterator嘛!这个疑问估计得看真正的for-of的内部实现源码才能明白了) 通过给iterator对象添加`[Symbol.iterator]`方法来返回iterator对象本自身,这样设计很重要的一点就是:`通用`。 怎么解释这个通用呢? 假设现在有一个方法A,方法A接收的参数是一个Iterable Object(方法A是干啥的你先不要关心),但是你现在传递了一个iterator对象给方法A,试想一下如果iterotor对象里没有`[Symbol.iterator]`方法,那方法A肯定不能顺利执行了。所以当给iterator对象添加`[Symbol.iterator]`方法后,任何需要参数为Iterable object的函数,你传给这个函数一个iterator对象,这个函数还是能正常work。希望我这样解释`通用`大家能明白。 ```js let arr = ['a', 'b']; let keysIterator = arr.keys(); // 获取遍历器 keysIterator[Symbol.iterator]() === keysIterator; // `keysIterator` 就是一个 Iterable iterator //for-of会重复的调用`keysIterator[Symbol.iterator]()`,来获取`keysIterator` //之后遍历它 for (let key of keysIterator) { console.log(key); // 0, 1 (the array indexes) } ``` ## 可能会中枪的小细节 因为iterator的`[Symbol.iterator]`方法会返回iterator自身,所以我们在用的时候一定要小心这个问题,不然代码执行出错了就坑爹了。 请看下面的例子: ```js let iterable = [1, 2, 3, 4]; let iterator = iterable[Symbol.iterator](); //先手动的遍历2次 iterator.next(); iterator.next(); //再调用for-of去遍历 for (let item of iterator) { console.log(item); // 3, 4 //注意了这里只遍历了2次,并不会从头开始遍历 } //如果你想从头开始遍历,你可以这样写 for (let item of iterable[Symbol.iterator]()) { console.log(item); // 1, 2, 3, 4 } ``` 原因就不在多说了,相信大家都懂的。 ## 总结 啊啊啊啊啊!谢谢你看完了这篇文章!希望我的介绍没问题,让你理解了iterables和iterators。 恩,文章差不多结束了,可能你有想继续学习ES6的内容,你可以看下,下一篇文章:[Generators](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-generatorfunction-objects)。 最后如果你想改善一下这篇文章,请点击这里:[JS Rocks repository](https://github.com/JSRocksHQ/jsrockshq.github.io)(额,这是英文原文),我们将会很高兴地与你讨论并接纳你的意见。 至于文章知识点总结就省略了,希望大家不要打我... ## 参考 - [ECMAScript® 2015 Language Specification - Ecma International](http://www.ecma-international.org/ecma-262/6.0/index.html) - [for...of - MDN](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/for...of) - [Iteration protocols - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) - [for-of reimplementation - Babel](http://babeljs.io/repl/#?experimental=false&evaluate=true&loose=false&spec=false&playground=true&code=let%20iterable%20%3D%20%5B'a'%2C%20'b'%2C%20'c'%5D%3B%0D%0Afor%20%28let%20item%20of%20iterable%29%20%7B%0D%0A%09console.log%28item%29%3B%20%2F%2F%20'a'%2C%20'b'%2C%20'c'%0D%0A%7D%0D%0A&runtime=true) - [Symbol - MDN](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Symbol) - [Object initializer - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer) - [Method definitions - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions) - [Arrow functions - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2015-11-17-use-node-build-static-blog.md ================================================ --- layout: post title: 使用Node.js生成一个静态博客 description: "静态博客wooden编写记录" tags: [Node,Javascript, ES6] image: background: witewall_3.png comments: true share: true --- >我github star最多的是我的博客,是用Ruby写的Jekyll搭建的,运行在支持它的gthub上。Jekyll很方便的一点是可以用markdown来编写你的博客,有一种极客的感觉。但是用Node.js怎么实现一个静态博客系统呢? ![github](http://ww2.sinaimg.cn/large/8ae515a4jw1ey4ctqpvhaj20sc02m0sz.jpg) ## 生成模板 首先要解决的问题是怎么用命令行来做一些操作。我打算用命令行生成模板,有一个模块`commander`实现很方便。 定义一个帮助命令 ```js program .command('help') .description('show help') .action(() => { program.outputHelp(); }); ``` 在定义其他命令 ```js program .command('create [dir]') .description('create empty project') .action(require('../lib/create')); program .command('preview [dir]') .description('preview your web page') .action(require('../lib/preview')); program .command('build [dir]') .description('build the project to HTML') .option('-o, --output ', 'build the project dir') .action(require('../lib/build')); program.parse(process.argv); ``` 我们定义了三条命令: 1. 创建 2. 预览 3. 生成 ## 创建 在创建的时候会把之前的一些模板文章生成到用户文件下,因为本模块是安装在全局的,运行该命名会把一些静态资源文件、模板、配置文件等输出出来供用户修改。 ```js try { //create template dir fse.mkdirsSync(path.resolve(dir, '_layout')); fse.mkdirsSync(path.resolve(dir, '_post')); fse.mkdirsSync(path.resolve(dir, 'assets')); fse.mkdirsSync(path.resolve(dir, 'posts')); // 复制模板文件 let tplDir = path.resolve(__dirname, '../tpl'); fse.copySync(tplDir, dir); console.log('create success!'); }catch(e) { console.log('create faild!'); console.error(e); } ``` ## 预览 预览就是用express启动一个web项目。开启一个Node.js web项目最简单无异于是用express框架了。我是用jade当做模板引擎,因为其提供的block和include实在是太强大了。 ```js let app = express(); app.use('/assets', express.static(path.join(__dirname, '../assets'))); app.set('port', (process.env.PORT || 3000)); app.set('views', path.join(__dirname, '../_layout/pages')); app.set('view engine','jade') ``` 我们设置好静态目录和模板引擎,就可以编写重要的路由了。在一个简单的博客系统,无外乎有两个重要的路由: * 首页 * 文章详情页 对应路由如下: ```js //render index app.get('/', (req, res, next) => { //... }); //render article app.get('/posts/:articleName', (req, res, next) => { //... }); ``` 最后,我们想启动的时候就能打开默认浏览器,这里,我参考了一下[新杰的代码](https://github.com/freeyiyi1993/mobile-test/blob/master/server.js#L25): ```js // open browers app.listen(app.get('port'), () => { let cmd = 'open "http://localhost:' + app.get('port') + '/"'; child_process.exec(cmd, (err, stdout, error) => { if(err) { console.log('error:' + error) } else { let url = 'http://localhost:' + app.get('port') + '/' console.log('Server started: ' + url) } }) }) ``` ### 解析markdown 解析markdown Node.js已经提供了封装好得模块`markdown-it`,我们设置参数调用就可以直接调用。 ```js let md = new MarkdownIt({ html: true, langPrefix: 'code-', }); ``` 一篇文章应该有他的一些特征,比如像Jekyll一样:标题、标签、背景图等。 我在markdown下有如下设置: ```html --- title : 我是标题 --- ``` 然后需要有一个[函数来解析](https://github.com/hacke2/wooden/blob/master/lib%2Futils.js#L55) 最后得到标题,我们命名文章的规则是日期+标题,和jekyll一样。 也需要解析一下 ```js let getArticleDate = title =>{ let arr = title.split('-'), result = []; for (let i = 0; i < 3; i++) { result[i] = arr[i]; } return result.join('-'); } ``` ### 异步调用 我们得到数据后,会将它给到jade,这是一个异步的过程,我用`Promise`来封装 下面是的获取首页 ```js let getArticle = name => { return new Promise((resolve, reject) => { let file = path.join(process.cwd(), '_post', name + '.md'); fs.readFile(file, (err, data) => { if(err) { reject(err); }else { let article = parseSourceContent(name, data.toString()); let html = md.render(article.content); resolve({ name : config.name, contact : config.contact, title : article.title, content : html, date : article.date, href : article.href }); } }); }) } ``` 下面是的获取某一篇文章 ```js let getArticle = name => { return new Promise((resolve, reject) => { let file = path.join(process.cwd(), '_post', name + '.md'); fs.readFile(file, (err, data) => { if(err) { reject(err); }else { let article = parseSourceContent(name, data.toString()); let html = md.render(article.content); resolve({ name : config.name, contact : config.contact, title : article.title, content : html, date : article.date, href : article.href }); } }); }) } ``` ### 渲染jade jade提供个强大的include 和 block。我们创建一个框架,其他页面继承它。 ```jade doctype html head meta(charset="utf-8") meta(name="viewport",content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no") title #{name} include ./includes/css block page_css body include ./includes/header block content include ./includes/js block page_js ``` 它包含css模板和js模板,页面放在content里 ```jade block content ``` 首页来继承它 ```jade extends ../layout block page_css link(href="assets/css/base.css",rel="stylesheet") link(href="assets/css/index.css",rel="stylesheet") block content h1.site-name #{name} article.container ul.article-list each article in articleList li(class="article") a(href="#{article.href}#{isBuild ? '.html' : ''}", class="article-title") #{article.title} div.abstract #{article.abstract} ``` 文章列表也也是如此,在此不展开了。 可以看出,预览是比较重要的一步,生成也是基于它来运作的。 ## 生成 我们在创建新的md文件后,需要将它编译成html,也是调用之前预览的方法来生成html。 ```js utils.getIndexData().then(data => { data.isBuild = true; console.log('build ' + path.join(viewPath, 'index.jade')) let html = jade.renderFile(path.join(viewPath, 'index.jade'), data); fse.outputFileSync(path.join(outputDir, 'index.html'), html); }); //build post utils.getFileList().forEach(filePath => { let fileName = path.basename(filePath, '.md'); utils.getArticle(fileName) .then(data => { console.log('build ' + path.join(viewPath, 'post.jade')) let html = jade.renderFile(path.join(viewPath, 'post.jade'), data); fse.outputFileSync(path.join(outputDir, 'posts', fileName + '.html'), html.toString('utf-8')); }); }); ``` ## 发布 想让这个命令不是用Node xxx.js来运行,直接是用xxx来运行,需要在bin目录下创建一个文件,将commande这个入口js拷进去,然后在开头输入 ```shell #!/usr/bin/env node --harmony ``` 因为此项目运用了es6的一些特性,需要使用`--harmony`来开启支持。 最后,使用`npm publish`发布,我已经将它发布在npm上了,起名`wooden`[https://www.npmjs.com/package/wooden](https://www.npmjs.com/package/wooden)。将一个demo部署在了开发机上[http://fe.sm.cn/xinglong.wangwxl/wooden/](http://fe.sm.cn/xinglong.wangwxl/wooden/) ![WOODEN](http://ww3.sinaimg.cn/large/8ae515a4jw1ey4cjpkywzj20xl0bkwf9.jpg) ## 遇到的一个坑 之前写项目都是用`__dirname`找当前目录,但是使用这种直接命令这种方式`__dirname`找到的是全局安装的路径,并不是当前命令执行的路径,最后路径会找不到,可以使用`process.cwd()`来找到当前命令执行的路径。 本文就此完毕,是不是和我现有博客的架构挺像? 项目地址:[https://github.com/hacke2/wooden](https://github.com/hacke2/wooden) **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2015-2-25-Variables-and-scoping-in-ECMAScript-6.md ================================================ --- layout: post title: 译-ES6里的变量和作用域 description: "在ES6中变量和作用域的使用方法" tags: [翻译] image: background: witewall_3.png comments: true share: true --- >http://www.2ality.com/2015/02/es6-scoping.html 原文链接 在本文将有大量的例子介绍在ES6中作用域和变量的使用方法 ## 1.块级作用域的let和const 让`let`和`const`创造块级作用域,他仅仅存在于包裹他们的最内层的块。下面代码演示了使用`let`修饰的`tmp变量`仅仅存在于最里层的`if`申明里。 ```js function func() { if (true) { let tmp = 123; } console.log(tmp); // tmp未定义 } ``` 相比之下,用var申明的变量在函数级作用域 ```js function func() { if (true) { var tmp = 123; } console.log(tmp); // 123 } ``` 块级作用域意味着你在函数里只要是两个不同的块,那么变量名称可以重复。(原文为**影子变量**) ```js function func() { let foo = 5; if (···) { let foo = 10; // shadows outer `foo` console.log(foo); // 10 } console.log(foo); // 5 } ``` ## 2.`const`创建不可变的变量 由`let`创建的变量是可变的 ```js let foo = 'abc'; foo = 'def'; console.log(foo); // def ``` `const`创建变量是不可变的 ```js const foo = 'abc'; foo = 'def'; // TypeError ``` 注意,`const`并不影响所赋的值是否可变,如果所赋的值是一个对象,那么并不能保证该对象不变。他只是保存一个对象的引用。 ```js const obj = {}; obj.prop = 123; console.log(obj.prop); // 123 obj = {}; // TypeError ``` 如果你想改变量是真正不可变的,那么直接[冻结他的值](http://speakingjs.com/es5/ch17.html#freezing_objects) ```js const obj = Object.freeze({}); obj.prop = 123; // TypeError ``` ### 2.1 循环体内的const 一旦`const`变量创建,那么他就不能改变。但这并不意味着你不能重新声明一个新值,比如在循环体内。 ```js function logArgs(...args) { for (let [index, elem] of args.entries()) { const message = index + '. ' + elem; console.log(message); } } logArgs('Hello', 'everyone'); // Output: // 0. Hello // 1. everyone ``` ### 2.2 什么时候我该使用let,什么时候该使用const? ```js const foo = 1; foo++; // TypeError ``` 如果你想创建的可变变量为基本类型,则,不能使用const。 不过你可以使用`const`修饰引用类型的变量。 ```js const bar = []; bar.push('abc'); // array是可变的 ``` 按照最佳实践,一般会把常量(真正不变的)使用大写来表示。 ```js const EMPTY_ARRAY = Object.freeze([]); ``` ## 3.临时禁区(TDZ) 被`const`和`let`修饰的变量我叫做它是`临时禁区` (TDZ)。当进入这个作用域,外界就无法访问这些被修饰的变量知道运行结束。 使用`var`修饰的变量没有TDZ。 * 当进入有`var`修饰的变量的作用域中,会在内存中立即创建空间,立即初始化变量,并且设置成`undifined`。 * 在执行过程中如遇到赋值关键字则给变量赋值,否则还是为`undifined`。 使用`let`关键字的拥有TDZ,这意味着它的生命周期如下: * 当进入有`let`修饰的变量的作用域中,会在内存中立即创建这个变量,不会初始化这个变量。 * 获取或设置未初始化的变量会导致引用错误(ReferenceError). * 在执行过程中如遇到声明处则初始化且给变量赋值,如果不赋值则为undefined。 `const`的机制与`let`相似,但他必须赋一个值且不能被改变。 在TDZ中,如果获取或者设置一个未初始化会抛出异常。 ```js if (true) { // 一个新的作用域, TDZ 开始 //tmp未初始化 tmp = 'abc'; // ReferenceError console.log(tmp); // ReferenceError let tmp; // TDZ 结束, `tmp` 被初始化为 `undefined` console.log(tmp); // undefined tmp = 123; console.log(tmp); // 123 } ``` 下面例子演示了TDZ是临时的(基于时间)的而不是基于位置的: ``` if (true) { // 一个新的作用域, TDZ 开始 const func = function () { console.log(myVar); // OK! }; // 在这里已经进入了TDZ,访问 `myVar` 会导致 ReferenceError let myVar = 3; TDZ 结束 func(); // called outside TDZ } ``` ### 3.1TDZ的类型检查 一个变量不能再TDZ里访问意味着你也不能在该变量使用`typeof` ```js if (true) { console.log(typeof tmp); // ReferenceError let tmp; } ``` 我不认为这将在实践中是一个问题。因为你不能有条件的给某一个作用域加上`let`修饰符。事实上你仍然可以使用`var`修饰符创建全局变量 ```js if (typeof myVarVariable === 'undefined') { // `myVarVariable`不存在,则创建它 window.myVarVariable = 'abc'; } ``` ## 4.在循环体的头部中使用`let`修饰符 在循环体中,你每次迭代重新绑定用`let`修饰的变量。允许你这样做的循环:`for`, `for-in`和`for-of` 。 ```js if (typeof myVarVariable === 'undefined') { let arr = []; for (let i=0; i < 3; i++) { arr.push(() => i); } console.log(arr.map(x => x())); // [0,1,2] } ``` 相比之下,用var声明的循环体中,,每次迭代室友一个单一的值 ```js if (typeof myVarVariable === 'undefined') { let arr = []; for (var i=0; i < 3; i++) { arr.push(() => i); } console.log(arr.map(x => x())); // [3,3,3] } ``` 为每次迭代得到一个新的绑定似乎有些奇怪,但当你使用循环创建函数(例如回调事件处理)它是非常有用。 ## 5.形参 ### 5.1 形参和局部变量 如果你声明的变量名正好与形参一致,那么会爆出一个静态错误 ```js function func(arg) { let arg; // static error: duplicate declaration of `arg` } ``` 在函数里面再嵌套一个块则会避免这个问题 ```js function func(arg) { { let arg; // 影子参数 `arg` } } ``` 相比之下,用`var`修饰的与形参同名的变量不会出现错误,表现的形式是覆盖了形参。 ```js function func(arg) { var arg; // does nothing } ``` ```js function func(arg) { { var arg; // does nothing } } ``` ### 5.2 默认形参与TDZ 如果形参有默认值,他们被当做一个序列 ```js // OK: 声明之后访问x function foo(x=1, y=x) { return [x, y]; } foo(); // [1,1] // 异常,在YDZ里试图访问y function bar(x=y, y=2) { return [x, y]; } bar(); // ReferenceError ``` ### 5.3 默认形参与TDZ 形参默认值的范围是独立于body的作用域(前者围绕后者)。这意味着“inside”定义的方法或函数参数的默认值不知道body的局部变量。 ```js // OK: 在x已经声明后y访问x function foo(x=1, y=x) { return [x, y]; } foo(); // [1,1] // 异常: `x` 试图在TDZ访问 `y` function bar(x=y, y=2) { return [x, y]; } bar(); // ReferenceError ``` ## 6.全局对象 JS中的全局对象(浏览器是`windows`,Node.js是global)的bug比特性还要多,尤其在性能这一块,这也就是不奇怪ES6有以下描述: * 全局对象的属性都是全局变量。在全局范围,`var` 和 `function` 声明创建这些属性 * 是全局变量但不是全局对象的属性。在全局范围,`let` 和 `const`, `Class` 声明创建这些属性 ## 7.函数的声明和类的声明 函数声明: * 块级作用域,像`let` * 在全局对象创建属性(在全局范围),像var。 * 声明提升:独立的一个函数声明中提到它的范围,它总是创建之初的范围 下面代码解释了声明提升 ```js { // Enter a new scope console.log(foo()); // OK, due to hoisting function foo() { return 'hello'; } } ``` 类的声明: * 块级作用域 * 不会再全局对象上创建属性 * 不会声明提升 类不升起可能令人惊讶,因为他们创建函数。这种行为的理由是,他们继承条款定义的值通过表达式,表达式必须在适当的时间执行。 ```js { // 进入新的作用域 const identity = x => x; //这儿是`MyClass`的TDZ let inst = new MyClass(); // ReferenceError //注意 `extends` class MyClass extends identity(Object) { } } ``` ## 8.扩展阅读 1.[Using ECMAScript 6 today](http://www.2ality.com/2014/08/es6-today.html) 2.[Destructuring and parameter handling in ECMAScript 6](http://www.2ality.com/2015/01/es6-destructuring.html) **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2015-3-13-anmi-strange-problem.md ================================================ --- layout: post title: Zepto CSS方法一个奇怪的现象 description: "Zepto CSS方法一个奇怪的现象" tags: [心声] image: background: witewall_3.png comments: true share: true --- 今天写活动页,打算封装一个JS逐帧运动的组件,但在调试的时候发现使用以下代码会出现问题: ```css curAnim.css({ 'background-position' : '-' + i * 75 + 'px 0px' }) ``` 在Chrome 模拟器下没任何问题,但是在iphone6下会出现一闪一闪的问题,百思不得其解,后来用JS原生方法解决了。 ```js curAnim[0].style.backgroundPosition = "-" + i * 75 + "px 0px"; ``` 在iphone6也正常了。 查阅了以下Zetop的css方法,其核心代码如下: ```js var css = '' if (type(property) == 'string') { if (!value && value !== 0) this.each(function(){ this.style.removeProperty(dasherize(property)) }) else css = dasherize(property) + ":" + maybeAddPx(property, value) } else { for (key in property) if (!property[key] && property[key] !== 0) this.each(function(){ this.style.removeProperty(dasherize(key)) }) else css += dasherize(key) + ':' + maybeAddPx(key, property[key]) + ';' } return this.each(function(){ this.style.cssText += ';' + css }) ``` 我上一句其实正在起作用的是下面: ```js this.style.cssText += ';' + css; ``` 于是我想,是不是`this.style.cssText`和`this.style.backgroundPosition`的区别?于是了然了:**cssText因为重新这设置了这个元素样式的会让浏览器将这个元素的整个都repaint+reflow,效率当然要比之变换一个样式的低**,算是分析完了,若对一个元素多次操作还是使用原生JS为妙。 **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2015-3-20-css-multi-row-overflow.md ================================================ --- layout: post title: 小tip:CSS3控制多行溢出 description: "打破CSS只能控制单行溢出的历史观点" tags: [CSS3] image: background: witewall_3.png comments: true share: true --- >以前做项目,对文字溢出只能进行单行溢出的控制: ```css white-space: nowrap; overflow: hidden; text-overflow: ellipsis; ``` 对于多行则无能为力,只能用渲染端来控制,这样有一个很大的问题:**无法达到响应式** 但是在WebKit浏览器或移动端(绝大部分是WebKit内核的浏览器)的页面实现比较简单,可以直接使用WebKit的CSS扩展属性(WebKit是私有属性)-webkit-line-clamp,这个历史问题也就不攻自破了,对应代码: ```css @media all and (-webkit-transform-3d){ .3-line{ white-space:normal; -webkit-line-clamp:3; //表限制三行 display:-webkit-box; overflow: hidden; text-overflow:ellipsis; -webkit-box-orient: vertical; } } ``` 对应SCSS代码如下: ```scss /** * multi lines overflow * @param {Number} $lineCount - count to display */ @mixin multiline-ellipsis($lineCount){ @media all and (-webkit-transform-3d){ &{ white-space:normal; -webkit-line-clamp:$lineCount; display:-webkit-box; overflow: hidden; text-overflow:ellipsis; -webkit-box-orient: vertical; } } } ``` 加这段媒体查询的原因是考虑只在webkit下用。直接引用这个代码片段传入参数即可。 对于过渡方案,有以下几种: 1.使用伪元素生成一个“...”(最好半透明背景),父元素定高`overflow:hidden` 2.以上功能由JS来代替[Clamp.js](https://github.com/josephschmitt/Clamp.js) **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2015-3-23-frame-animation.md ================================================ --- layout: post title: 逐帧动画两种实现方式 description: "分别用JS/CSS实现逐帧动画" tags: [CSS3] image: background: witewall_3.png comments: true share: true --- >由于使用gif,我们不能将其控制(播放、暂停、播放次数),所以逐帧动画一般使用代码实现,下面我们来介绍两种方法。 **原理就是时时刻刻改变他的`position`**。 ## 1.JS逐帧动画 JS就是调用定时器,这边有一个小技巧:直接改变一个经测试会有闪烁的情况,再加一个重叠替换运动 JS Bin 每次改变位置,效率不高。简单封装了一段JS插件 ```js ;(function($) { 'use strict'; $.extend($.fn, { requestAnimFrame: function(opt){ var defined = { top:0, left:0, position:'absolute' }, option = $.extend(defined, opt), i = 0, flag = false, curAnim = null, self = this, width = 0, isRunning = false, maybeAddPx = function (value) { return (typeof value == "number") ? value + "px" : value; }, /** * 标准化requestAnimFrame * @param {[type]} ){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function( callback ){ window.setTimeout(callback, 1000 / 60); }; })( [description] * @return {[type]} [description] */ requestAnimationFrame = (function(){ if(option.time) { return function( callback ){ window.setTimeout(option.time); }; } return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function( callback ){ window.setTimeout(callback, 1000 / 60); }; })(), //添加两张图,解决一轮动画结束闪屏问题 anim_1 = $('
', { css : { background : 'url(' + option.url + ') no-repeat scroll 0px 0px transparent' } }), anim_2 = anim_1.clone(), init = function() { self.append(anim_1).append(anim_2).css('position', 'relative'); }, width = option.length / option.step, go = function() { if (i > step) { i = 0; } flag ? (curAnim = anim_1) : (curAnim = anim_2); flag = !flag; curAnim[0].style.backgroundPosition = "-" + i * width + "px 0px"; //使用Zepto方法会闪动,具体看博文:http://www.hacke2.cn/anmi-strange-problem/ //curAnim.css('background-position' , '-' + i * 75 + 'px 0px') self.append(curAnim); i++; console.log(isRunning) isRunning && requestAnimationFrame(go) }; init(); return { run : function() { isRunning = true; requestAnimationFrame(go); }, stop : function() { isRunning = false; }, isRunning : function() { return isRunning; } }; } }) })(Zepto); ``` 调用的时候这样调用: ```js var ra = scope.find('#divParent').requestAnimFrame({ step : 80, url : 'img/1-slow.png', length : 6000, time : 12 }); ra.run(); ``` 自己完成两个DOM的COPY,html结果如下: ```html
``` 提供了几个接口:run、stop、isRunning ## 2.CSS3逐帧动画 CSS3方式实现当时比用JS效率高很多,许多优化在浏览器底层完成。之前为什么不用有两点考虑: 1.兼容性 2.每一帧都要手动去写 但是在移动端CSS3已经支持很好了,但每一帧都得自己写太痛苦,还好在一篇博客看到有一个step函数供我们调用 [https://idiotwu.me/css3-running-animation/](https://idiotwu.me/css3-running-animation/)。我们将它整合一下: JS Bin 怎么样,是不是很简单,中间的帧数让浏览器给我们计算就可以了。 ,关于`step`,这边有篇文章介绍的很详细[CSS3 timing-function: steps() 详解](https://idiotwu.me/understanding-css3-timing-function-steps/) **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2015-3-4-to-teacher.md ================================================ --- layout: post title: 给老师的一些课程主题 description: "前端课程的参考主题" tags: [翻译] image: background: witewall_3.png comments: true share: true --- >我们实验室有一门内部课程,专门讲前端开发,每年的主题都不一样,今年老师让我们建议几个,我罗列了一下几个主题供老师参考: ### 1. 前端工程化 前端工程化是将代码、资源(图片等)的压缩、合并、检错等一些列使用程序来自动完成。有了它可以大大提高开发效率。目前比较流行的国外有grunt,gulp等,国内有fis等,建议讲课时可以讲讲主流前端工程化工具的使用和思考如果将至融入实验室工作的开发流程。 ### 2. 前后端分离的思考 以前前端后端开发分离不够,如修改jsp模板前端需要和后端协调,一些数据协议要沟通,一些代码逻辑不能复用(如前后端的数据校验)。支付宝提出使用Node.js在前端和后端中间再加一层,首屏交给Node.js来渲染,二次请求可以通过ajax动态调用。详情可以参照淘宝UED[《前后端分离的思考与实践》](http://ued.taobao.org/blog/2014/04/full-stack-development-with-nodejs/) 一个趋势,大家可以一起学习一下,可以进行头脑风暴。(但实验室这种开发模式是一个人前后端都做,所以没有沟通的成本,但很难在某一领域专一) ### 3. CSS3动画 现在CSS3新起,动画也有以前的Flash慢慢过渡到用CSS3来实现。使用动画让活动页内容更加丰富活泼,讲的时候建议讲讲重要api及综合案例。参考: * 黄薇 [《Web高性能动画》](http://melonhuang.github.io/sharing/slides.html?file=high_performance_animation#/) * [奇舞团《CSS动画使用技巧》](http://www.75team.com/archives/793) * Webrebuild [《CSS3 动画》](http://daxue.qq.com/content/content/id/1676) ### 4. ECMA Script 6 AND IO.js ES6将是今年的趋势,这次草案规范了Javascript的类、模块化、Promise、Genertor等重要API,IO.JS作为Node.js的分支,默认开启了ES6,服务端JS是必不可少的 ### 5. MVC and MVVM in JavaScript 随着SPA应用的增多,大型前端网页的代码组织不再是简单的JQuery对元素进行操作,而是引入了以Backbone.js代表的MVC框架,以Angular.js代码的MVVM框架。建议实验室同学一起学习学习这些框架的理念。 ### 6. Native App VS Web App 本地APP和WEB APP一直孰好孰坏而争吵,可以谈谈两类开发的优劣。另外facebook最新一版使用React.js开发Native App,也以为这JS工程师在无学习成本转向Native App,也可以给大家分享分享React.js ### 7. Web Components 传统的组件开发方式必须要引一堆HTML,CSS,JS,而Web Components提出只是使用简单自定义标签就可以代替传统开发模式,加快了开发的速度,减少了复制时的错误率,这是未来的开发方向,建议做一些DEMO与大家分享。 ### 8. Canvas & SVG 随着HTML5新起,Canvas & SVG就尤为重要了,他们可以做游戏、数据可视化等强大的功能。建议讲的时候将一些重要API和DEMO。 **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2015-3-7-share-jsbin.md ================================================ --- layout: post title: 我在jsbin里存的好东西 description: "在jsbin里存的一些代码片段" tags: [心声] image: background: witewall_3.png comments: true share: true --- >看到一些有用的代码片段,我就喜欢收藏到我的jsbin里。今天和大家分享一下 1.canvas画出圆环进度条 代码注释很多,初级入门都能看懂,作为活动页很适用 [http://jsbin.com/viribi/1/](http://jsbin.com/viribi/1/edit?html,output) ![](/images/article/jsbin-share/1.png) 2.无刷新分页 推荐用ajax方式,这个代码是讲所有的数据都加载出来的处理方式 [http://jsbin.com/poxawo/1/](http://jsbin.com/poxawo/1/edit?html,output) ![](/images/article/jsbin-share/2.png) 3.富文本编辑器 火狐开发者网站的,代码不多,使用`document.execCommand`文本样式操作。 [http://jsbin.com/belafa/1/](http://jsbin.com/belafa/1/edit?html,output) ![](/images/article/jsbin-share/3.png) 4.太极 用CSS3画的太极 [http://jsbin.com/jigiko/2/](http://jsbin.com/jigiko/2/edit?html,output) ![](/images/article/jsbin-share/5.png) 5.伸缩式布局 在群众看到一个小伙写1024这个游戏时用的布局,关键是`padding-top:100%`这行代码。在实际用用中,除了上面9格布局,还适用于图片的自适应性。当然也可以使用`padding-bottom`,后面的值为图片的宽高比例 [http://jsbin.com/foruli/3/edit](http://jsbin.com/foruli/3/edit?html,css,output) ![](/images/article/jsbin-share/4.png) 6.动画购物 类天猫的购物、收藏、红心等功能,有动画效果 [http://jsbin.com/ziguke/1/](http://jsbin.com/ziguke/1/edit?html,css,js,output) ![](/images/article/jsbin-share/6.png) 7.条件解析 由于UC某些接口有版本限制,我在写的时候用JSbin边写边调,也共享出来吧 [http://jsbin.com/kajixi/3/edit](http://jsbin.com/kajixi/3/edit) ![](/images/article/jsbin-share/7.png) 8.Promise 几行代码实现的Promise [http://jsbin.com/batuyo/1/edit?html,js,output](http://jsbin.com/batuyo/1/edit?html,js,output) 9.loading 动画 之前我见过的这个loading动画使用了很多div标签,但是这个缺只使用一个标签,果断收藏了 [http://jsbin.com/yalepo/1/edit?html,css,output](http://jsbin.com/yalepo/1/edit?html,css,output) ![](/images/article/jsbin-share/8.png) 10.canvas笑脸 算是canvas最简单的api使用了 [http://jsbin.com/vaqoba/2/edit](http://jsbin.com/vaqoba/2/edit) ![](/images/article/jsbin-share/9.png) 11.css3瀑布流 群友推荐的,使用CSS3实现瀑布流 [http://jsbin.com/lihamu/1/edit?html,output](http://jsbin.com/lihamu/1/edit?html,output) ![](/images/article/jsbin-share/10.png) 12.妙味首页菜单 以前看妙味首页就感觉特别炫,一位博客园仁兄将至抠出来了,收藏! [http://jsbin.com/hoyija/1/edit?html,output](http://jsbin.com/hoyija/1/edit?html,output) ![](/images/article/jsbin-share/11.png) **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2015-5-18-how-i-m-using-es6-modules-in-production.md ================================================ --- layout: post title: 译-我是怎么在项目中使用ES6模块化的 description: "如何让ES6 module 和 AMD module一起工作" tags: [ES6] image: background: witewall_3.png comments: true share: true --- >http://jsrocks.org/2015/05/how-i-m-using-es6-modules-in-production/ 原文链接 我使用 [ES6 modules](/categories/modules/)工作已经有一段时间了,今天我就向大家分享下我是怎么使用ES6 moudule的。 首先,向大家介绍一下Babel ,[Babel](https://babeljs.io/)是用来JS转译的整合工具,它是一个非常活跃的项目,而且它几乎覆盖了所有现代JavaScript特征。(注:JS转译(Javascript Transpiler)请自行谷歌) Babel完美支持模块化,你可以自己决定你代码的风格,你可以使用AMD,Common,UMD这些规范,而且你甚至能自定义模块规范。 在我的公司里,我们开发了一个基于AMD modules规范的框架(当然尚未开源),用来构建我们的应用。 相信我:对于开发大型的应用,使用AMD规范仍然是一个很好很好的解决方案。我们不能再把所有js代码反正一个文件里,这样是不行的。 悲剧的是我们现在很多些应用的解决方案是类似于[Webpack](http://webpack.github.io/),要想迁移到AMD modules可不是那么容易,而我们公司关于模块化的解决方案是很实用的(尚未开源)。 注:在本文中“解决方案”都是指JS模块化解决方案。 微模块策略 这个策略对我来说很有用。 正如我上文说的那样,我们最终产出模块必须要要是AMD module,但是当某一个AMD模块需要依赖其他模块时,我叫那个模块(AMD模块依赖的模块)叫做微模块。 微模块并不会被应用的每个模块都用到,但是利用它能帮助我们组织代码。 以下代码演示了我们怎么使用微模块: ## 微模块策略 这个策略对我来说很有用。 正如我上文说的那样,我们最终产出模块必须要要是AMD module,但是当某一个AMD模块需要依赖其他模块时,我叫那个模块(AMD模块依赖的模块)叫做微模块。 微模块并不会被应用的每个模块都用到,但是利用它能帮助我们组织代码。 以下代码演示了我们怎么使用微模块: ```js import config from './config';//导入ES6模块 import { globalpkg } from './config';//动态注入ES6模块参数 import factory from './factory'; zaz.use((pkg) => { "use strict"; config.dynamic.globalpkg = pkg; pkg.require(['modFactory'], (modFactory) => { modFactory.create(pkg.utils.deepMerge(config._static, factory)); }); }); ``` 我们在代码的顶部导入了一些模块,并且在我们的AMD模块里还使用了这些导入的模块。在其他的应用中并不会用到这些ES6模块,但是使用了这些微模块后,编译后产生的最终源码有更高的可读性。 这是config模块的代码: ```js const githubURL = "OUR GITHUB URL HERE"; const staticServer = "http://s1.trrsf.com"; const testsPath = `zaz-${type}-${name}/tests/index.htm?zaz[env]=tests`; const name = "stalker"; const type = "mod"; const version = "0.0.1"; const state = "ok"; const description = "JavaScript API to deal with user data"; let globalpkg = null; // default export const config = { _static: { name, version, state, description, docs: `${githubURL}/pages/terra/zaz-${type}-${name}`, source: `${githubURL}/Terra/zaz-${type}-${name}`, tests: `${staticServer}/fe/${testsPath}`, dependencies: ['mod.wilson'] } }; export default config; ``` 这是项目的源码结构: └── src ├── _js ├── config.js ├── environment.js ├── helpers.js ├── factory.js ├── methods.js └── mod-stalker.js   └── mod-stalker.js 我把一些AMD模块的逻辑放进了微小的ES6模块里。 这对于构建处理是很简单的:Babel转译ES6的Javascript代码为ES5的代码;使用导入CommonJS模块的方式来导入微模块。最后用[Browserify](http://browserify.org/) 打包代码。 哈哈!最终生成的代码符合AMD模块规范了。 ## 下一步 Sourcemaps比起Browserify来说,Sourcemaps不能很好的处理这个编译的流程。 也许这里能有更好的实现方式。 我们现在可以开始使用ES6新特征和模块化来重构我们的框架了。 你甚至可以自己实现模块加载规范给你的程序,但是我认为这可能不是一个好方法,有可能你会需要完全重写现有的模块加载系统。 ## 结论 现在模块规范已经完成了,而且是非常成熟和有效的。 然而,web浏览器至今没有提供js模块加载的原生API,所有像AMD或者CommonJS就显得格外重要了。 现在我们已经体会到ES6模块语法的好处了,可以让我们的代码拥有更高的可读性和简洁性。 现有的工具,比如Babel和Browserify能减少我们开发的痛苦,相信在不久的将来等ES6普及后,我们就能简化js开发流程,这样就能舍弃这些构建打包工具了。 **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2015-9-16-express-node-act.md ================================================ --- layout: post title: express介绍及实战 description: "神马前端9月新人分享" tags: [分享] image: background: witewall_3.png comments: true share: true --- >上周5和组内分享了一下express介绍和实战,制作的ppt分享给大家: [express介绍和实战](http://www.hacke2.cn/ppt/express.html) **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2016-1-1-review-2015.md ================================================ --- layout: post title: 2015年终小结 description: "2015回顾" tags: [Javascript, ES6] image: background: witewall_3.png comments: true share: true --- >随着凌晨12点的钟声,2015就这样过去了,这一年是我从一个学生状态转变为工作状态的一年,对于我来说是人生十分重要的一年。 [《该是做做计划的时候了!》](http://www.hacke2.cn/2015-plan/)这边文章是今年年初写的,当时大致列了一下几点: * ES6 * HTML5和CSS3 * backbone,ng等MVC框架 * 属于自己的前端框架 * 算法 * 设计 * 读书 * 毕业设计 * 锻炼 * 独立能力&自理能力 前6个是涉及技术的。 ES6算是零零碎碎学了一些,也在简单的作品里练习了一把。es6在今年改名为es2015,可能也是希望在2015年公司也开始把这个用起来,可惜浏览器支持不够,本来以为并不能现在马上就运用。所幸有大杀器babel,可以将es6代码转化为es5代码,据说编译后的代码甚至优于源代码,今年也是超乎想想的在各个公司线上项目实践起来了。可惜编译后文件过于庞大,目前之适用于pc端,移动端如果对速度有特殊要求的业务并不适用,比如神马搜索主搜业务。但动效平台这种基于Node.js的项目或者其他垂搜感觉可以尝试一下。D2上李成银分享了《使用 ES6/7 特性开发 Node.js 项目》,回来也给部门小伙伴分享了一下,实现了年初的计划。`完成` HTML5和CSS3在移动端可谓是玩的风生水起,这也是我们部门的一个优势:专注移动端开发。在移动端上不用考虑IE6/7/8等不支持css3特性的浏览器,所以平时写sc时几乎天天和h5,css3打交道,又加上今年神马重点项目是蓝光项目,里面使用了大量的创新交互,加上h5,css3助力,这一块更加稳固了。`完成` 年初的时候大家讨论的最多的就是angular,当时计划也学学backbone, angular等基于mvc的框架,因为自己也有java的功底,所以感觉这些学起来应该不难,但是万万没想到今年是react.js唱戏,衍生出了react-native、relay、flux等基于react.js的生态圈,可谓今年是racet.js的胜利年,各个分享会分享主题多一半也是react.js。最近微博真阿当和各路大侠引发的关于前端框架的争论也颇有看点,总之前端轮子层出不群,最终哪一个能活下来,哪一个才是真正能提高前端的开发效率,还有待观察。但我感觉mvvm确实非常适用于一些业务复杂的场景。神马这边有小康在明星垂直上实践的`vue.js`,也是今年的一个颇具闪光点的mvvm框架。`未完成` 年初本来想的是有一个自己的前端框架,类似于jq等,每一个项目都能用起来,目的是快速构建一个web项目吧,谁知道最后写了一个博客生成器(类似于hex),已经在npm上发布[wooden](https://www.npmjs.com/package/wooden),也算是实现了计划吧。`完成` 算法、设计这两块并无任何建树,感觉应该是火候还未到,或者还没有接触到算法相关的业务吧。。 书倒读了几本Node.js相关的书,但是当时想看一点非技术的书的。。今年很庆幸的是以后的方向定了,想以后就往Node.js方向走走,正好在阿里的大环境下学学Node.js。阿里应该是国内Node.js沉淀最多的公司吧。。`未完成` 非常开心的是毕业设计业顺利过了,之前5月份回学校还有点担心呢。。后来都段时间调设备调到了2点钟。。最后感谢老师的照顾,拿了个校优^_^。`完成` 7月份去公司坚持跑了一段时间的步,当时去公司旁边的学校:北京语言大学和北京地质大学的操场上跑5圈,下雨也是在公司跑步机上坚持,结果没坚持下来。。。`未完成` 十月一号来杭州,二号就把被子什么全买好已经入住了,一个人在杭州生活了100天乐,总体感觉还行,希望在新的一年里,家里收拾更整洁一点,穿的也不求时髦,只求干净。`完成` 2016年将会是正式工作的第一年,希望心中所想能顺顺利利的完成。 最后,跨年夜和大学同学们分享了很多以前的回忆,希望大家能在以后的日子里健康快乐,事业有成。 ![11382](http://ww4.sinaimg.cn/large/8ae515a4gw1ezjutg9dixj20zk0k0q6z.jpg) **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2016-2-1-configuring-babel-6-for-node-js.md ================================================ --- layout: post title: 译-为你的Node.js配置babel 6 description: "babel 6基础教程" tags: [Node.js, ES6] image: background: witewall_3.png comments: true share: true --- >原文:http://jsrocks.org/2016/01/configuring-babel-6-for-node-js/ 如果你像我一样在你的Node.js应用上还在使用老的ES5 js代码,那么你可以抛弃之前的习惯,现在就可以使用JavaScript ES2015的特性和ES2016的标准在你的Node.js应用上。ES2015和ES2016让JavaScript宛如清风般一样开发,但是,不是每一个ES2015特性在我们心爱的[Node.js](https://nodejs.org)上支持。 这时候[Babel](https://babeljs.io)就派上用场了。Babel是一个让ES2015和ES2016代码转换成ES5甚至是ES3的编译器。简单来说,它会转换你的JavaScript代码,让它在Node.js应用上非常愉悦的用起来。 **小记:** Node.js已经支持多种ES2015的功能,如果你不想转化你的ES2015代码,你可以在启动Node.js是使用 `--harmony` 标签去开启一些已确定标准化的特性。为了使用更多实验性特性可以使用更激进的命令: `node --v8-options | grep harmony` ,但是请注意即便是最新版本的Node.js(在写本文的时候最新版本是5)也不是所有的特性都被支持。这些特性往往是不稳定或不完整的。所以为了保持可读性常常不使用这些特性。 ### 下面做一些假设: - 你了解如何编写[Node.js](https://nodejs.org)应用。 - 你会使用npm来安装软件包。 - 你已经安装了Node.js和npm。 - 你喜欢使用一些命令行命令。 - 最好也了解一点ES2015的语法,当然这个不是必须的。 ### 一些例子 有没有一些可运行的例子而不仅仅是阅读代码? 这儿有[可运行的例子](https://github.com/abdulhannanali/babel-configuration-tutorial) ### 安装和开始学习Babel 有很多方法让你可以安装并使用Babel,但这这里我们讨论的是如何让babel-cli运行。 在`code` **文件夹** 下创建一个简单的包含ES2015代码的Index.js文件: ```js function* jsRocksIsAwesome() { yield "JS Rocks is Awesome"; yield "JS Rocks says JavaScript Rocks"; return "because JavaScript really rocks"; } var jsRocks = jsRocksIsAwesome(); console.log(jsRocks.next()); console.log(jsRocks.next()); console.log(jsRocks.next()); ``` 我们将在下一个命令下安装**babel-cli**。他会在当前项目安装最后一个稳定版本的babel-cli并且也会追加在package.json的devDependencies里。 npm install --save-dev babel-cli 现在如果你运行 babel code/index.js -d build/ 你将会看见你写的相同代码出现在build/index.js文件夹里,所以Babel的 **插件** 和 **预设** 来临了。 ### 插件和预设 babel本身并没有做很多事情,但是通过插件和预设可以实现很多功能。我们希望可以通过babel在我们的代码中使用es2016和es2015所有的优美语法。 为此我们需要安装两个预设文件在 `devDependencies` 里。 - [es2015](https://babeljs.io/docs/plugins/preset-es2015/) - [stage-0](https://babeljs.io/docs/plugins/preset-stage-0/) 运行下面的命令安装预设文件: npm install --save-dev babel-preset-es2015 babel-preset-stage-0 babel拥有广泛的插件在[这边获得](https://babeljs.io/docs/plugins/) 现在你需要在执行命令的时候包含这两个预设。 babel --presets es2015,stage-0 code/index.js -o build/app.js 现在你会看到正常的ES5代码已经出现在 `app.js`了,它也叫 `编译后的代码` (这是js世界里的一个术语)。你可以使用以下命令运行这些代码: node build/app.js ### 使用Babel来配置一个合适的编译环境 这是非常神奇的,但是怎么样才能更好地运用在Node.js开发上? #### babel的配置文件: .babelrc `.babelrc` 是一个非常简洁的JSON文件,它可以分离出你的Babel相关的配置。它也是非常易于上手的。以下是本教程的`.babelrc` 文件。 ``` { "plugins": ["es2015", "stage-0"] } ``` 你可以配置其他[`.babelrc` 选项](http://babeljs.io/docs/usage/options/),确保它和你一样强大。 对于本教程来说这是非常多的配置。现在,每当我们添加或删除插件,我们仅仅改变以下配置选项而不是去修改他的运行命令,是不是很简单! 现在,如果你运行: babel -w code/ -d build/ 他会从 `.babelrc` 读取 **预设** 去编译在 `code/` 里的代码并且生成编译好的JavaScript文件在 `build/` 文件夹并且不会结束此命令。注意这个 `-w` 标志:如果你对 `code` 文件夹修改,它会 **监听** 和重新编译这个文件夹下的代码,酷!我现在所说的是不是非常神奇。 #### 使用source maps在你的文件 如果你在思考虽然它非常酷和有趣但是非常难于调试编译后的代码。你不用对此担心。Source maps的思想就是出于这个目的。Source maps会告诉Node.js这个错误的 **源文件** 而不是 **编译后的文件** ! 这边有一个 `code/error.js` 文件会抛出一个异常在生成器的第二次 `yield`后。编译后的代码完全和源文件是不同的。 ```js function* errorFulGenerator() { yield "yo"; throw new Error("source maps are awesome"); return ""; } var errorGen = errorFulGenerator(); errorGen.next(); errorGen.next(); ``` 我们使用如下命令去给 **编译后的文件** 生成 **source maps**。*注意使用`--source-maps` 标签*: babel code/ -d build/ --source-maps 现在当我们遇到错误的时候我们会获得有用的调试信息,如下: errorGen.next() ^ Error: source maps are awesome at errorFulGenerator (/home/programreneur/Programming/githubRepos/babeljs-short-tutorial/code/error.js:3:9) at next (native) at Object. (/home/programreneur/Programming/githubRepos/babeljs-short-tutorial/code/error.js:10:10) at Module._compile (module.js:425:26) at Object.Module._extensions..js (module.js:432:10) at Module.load (module.js:356:32) at Function.Module._load (module.js:313:12) at Function.Module.runMain (module.js:457:10) at startup (node.js:138:18) at node.js:974:3 这个就是使用 source maps 的方法。 #### 设置npm命令 为了能每一次都非常简单的使用编译命令,你可以更新你的 `package.json` 文件去为Babel加一个构建命令。在 `package.json` 的 `script` 对象你可以如下添加构建命令。 ```js "scripts": { "build": "babel -w code/ -d build -s" } ``` 现在,我们运行: npm run build 从今天开始就应该全面享受 ES2015/ES2016 带来的好处了! #### 了解更多关于Babel的知识 这是一个Babel基础教程,但是Babel的世界才刚刚开始。它有给力的社区作支撑并且已经在IT世界有了响当当的名声。Babel也支持大多数构建工具例如[Grunt](https://www.npmjs.com/package/grunt-babel) 和 [gulp](https://npmjs.org/package/gulp-babel/). 你可以在 [Babel Website](https://babeljs.io/docs/setup/)了解到相关信息。 这儿有一些资源可以帮助你在Bable的世界里打怪升级: - [Learn ES2015 and Babel using this detailed tutorial](http://ccoenraets.github.io/es6-tutorial/index.html) - [Read the Babel docs on setting up Babel (They're helpful)](https://babeljs.io/docs/setup/) #### 源码、贡献和感谢 本教程的源码在[这个仓库](https://github.com/abdulhannanali/babel-configuration-tutorial)。 如果你发现一下错别字或者有一些更新,请使用issues或者给[我们的Github 仓库](https://github.com/abdulhannanali/babel-configuration-tutorial)发起PR。 我也非常感谢[Fabrício Matté](http://ultcombo.js.org/) 审阅这篇文章并且把它发布在[JS Rocks](https://github.com/JSRocksHQ/jsrockshq.github.io/) 并且也做了一些更正。 **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2016-6-19-think-in-react-redux-1.md ================================================ --- layout: post title: React + Redux 入门(一):抛开 React 学 Redux description: "redux 入门" tags: [Redux, React] image: background: witewall_3.png comments: true share: true --- ## redux简介 Redux 是一个改变状态(state)的模型,这个模型通过一个单向操作的方式来改变状态。现在网上教程一言不合上来就是 Redux + React 的综合运用,经常搞的人一脸懵逼。其实 Redux 和 React 完全解耦,并不是 Redux 非得和 React结合才能使用,而只是 React 结合 Redux 会事半功倍。本系列主要也讲得这个。 对于日益复杂的 Javascript 应用来说,Javascript 需要管理非常多的 state。包括本地尚未持久化到数据库的数据、UI状态等等,而且这些状态有可能是相互关联的,一个状态的改变可能会引起另外一个状态的变化,如果用命令式编程将会变得异常复杂以及难以维护。Redux 作为一个专门关联 state 的框架应用而生,而这种单向数据流的思想也让 Redux 成为一个现代框架。 ![](http://img4.tbcdn.cn/L1/461/1/3db24afe31e7218b4ac6cc74497204250b2f5f3a) redux介绍 ### redux三大原则 Redux 有三大原则: - 单一数据源 - state 只读 - 只用纯函数来修改 ### 单一数据源 整个应用的 state 都是保存在一个对象树中,而且这个对象树存在唯一一个 store。这个 store 我们通过`redux.createStore创建`,通过以下代码获取: ```js store.getState() ``` ### state 只读 改变 state 只能通过 dispatch 一个 action 才能修改。 action 其实就是一个简单对象,其中type是必填项,以便 区分是哪一个 action。 ```js store.dispatch({ type: 'ALL' }); ``` 为了方便给 action 传递数据,一般来说我们会把上边参数对象封装成方法。如: ```js const create = (item)=> { return { type: 'CREATE', item } } ``` 即便在小的功能也得这样修改 state。比如实现表单的双向绑定。我们给一个input绑定一个在`onChange`事件,然后在`onChange`里拿到当前的 value,dispactch 一个 action 通知 reducer 改变给当前 dom 绑定的state(根据props传递),这样才能实现双向绑定。 ### 使用纯函数来执行修改 何为纯函数?简单来说就是函数的输出完全由输入所决定,运行过程不依赖于系统的状态和上下文环境,运行过程不改变它作用域之外的环境状态。详情可以参考月影的:[高阶函数对系统的“提纯”](https://www.h5jun.com/post/higher-order-function-play-with-pure-function.html) 这个纯函数在 Redux 里叫做 Reducer,它接收先前的 state 和 action,并返回一个新的 state,由于它是纯函数,所以它的结果是可预测的,这样为编写单元测试创造了条件。 ### 一个实例 介绍完三大原则,我们认清了redux的三个非常重要的组成部分: - action - reducer - store action 通知 reducer 修改 state,store 管理 state。非常简单。介绍一个非常简单的demo: 运行环境: node v6.1.0 ```js const redux = require('redux'); const createStore = redux.createStore; const ActionType = { ADD: Symbol() } ``` 首先我们引入 Redux后,编写一个简单的常量类,这个类里存储着一些不同的类型。注意一般会使用字符串来区分,为了避免出现无意义的字符串,我们使用 ES6 的 `Symbol`。 我们定义一个元素初始状态的 state: ```js let initState = { products: [] }; ``` 接下来,我们就可以写一个 action,比如新增产品 ```js const addProducts = (product)=> { return { type: ActionType.ADD, product } } ``` 我们现在需要一个 reducer 来改变我们的状态 ``` const getProducts = (state=initState, action) => { switch(action.type) { case ActionType.ADD : return Object.assign({}, state, { products: state.products.concat(action.product) }); default : return state; } } ``` 然后将 reducer 放入 store 中进行测试 ```js let store = createStore(getProducts); console.log(store.getState()); store.dispatch(addProducts(1)); console.log(store.getState()); ``` 输出结果 ```js { products: [] } { products: [ 1 ] } ``` 注意:分析 createStore 的源码可知,在初始化的时候他自己会 dispatch 一个 action: ```js export var ActionTypes = { INIT: '@@redux/INIT' }; ``` 会对当前 state 初始化。 至此,我们使用 Redux 编写了一个无 React 的例子。也对 Redux 有了一个基本的认知。 **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: _posts/2017-01-22-review-2016.md ================================================ --- layout: post title: 2017 is Comming description: "致已经逝去的2016和即将到来的2017" tags: [心路历程] image: background: witewall_3.png comments: true share: true --- >在雨中我送过你 > >在夜里我吻过你 > >在春天我拥有你 > >在冬季我离开你 > >有相聚也有分离 > >人生本是一曲戏 > >有欢笑也有哭泣 > >不知谁能 > >谁能躲的过去 2016就这样过去了,本想回顾一下,却发现自己没有做那年的规划。发生了很多事情,有遗憾,也有意外。 去年参加 jsconf 的时候,一些老外演讲的愣是听不懂,当时好后悔英语没有好好学,再加上在 youtube 偶尔看看国外的演讲论坛,所以在北京的出差的时候下决心把英语再学一学,不然真的感觉没几乎了。上高中的时候鼠目寸光,到现在觉得英语绝对是非常非常重要的一个技能,因为信息不对称对于我们这个快速发展的行业非常非常不利,也不想咀嚼别人嚼过的东西,所以一回到杭州就报名了个英孚教育,当时觉得有意思的点是每天晚上和其他不认识的小伙伴一起上多对一的对话课,每天的主题都不一样,坚持了两周后,觉得这个不是自己想要的,因为水平层次不齐,往往一节课得脱到一个小时以上,有一节课甚至脱到了一个半小时。。这对于我们这种时间很少的人是太可怕了。。所以现在的规律一般是一周一单元 + 1v1的私教。目标是17年的 jsconf 能听懂。 以前完全觉得胖一点无所谓,后来觉得一个程序员,如果思想活跃,四肢又发达,多好啊。。索性报了一个健身班,每周去四天,基本是40分钟有氧+20分钟无氧,现在早晨的体重是136,希望能达到125,让自己有漂亮的肌肉。我觉得每天坚持跑步是一种积极的生活态度,合理健康的饮食,以前自己吃的东西太油腻太不健康,2016年已经意识到这一点,希望今年能将健康的生活态度继续保持下去。去年还有一件非常自豪的事情是把抽了10年的烟戒了。 2015年看了很多技术书,去年开始觉得非技术的书也很重要,已经看了tinyfool的《技巧》,村上的《且听风吟》,人生不只是为了吃饭的技能,还有应该有强大的精神世界,我打算今年看10本书,在此里一个flag。 今年也想学一门乐器了,能会一个乐器的程序员特别帅,也许能在音乐上有点代码灵感呢?说不准,想学的可能是吉他,也有可能是电子琴。还想抽空把车学了,不知道有没有做这个事情的动力。。 至于生活吗。。想和爱的人去很多很多地方。。 文章开头是韩寒的《在雨中》,觉得他唱的很好听,有相聚,也有分离,希望自己能拥抱新的世界。 **文章来自 [{{ site.url }}]({{ site.url }})** ================================================ FILE: about.md ================================================ --- layout: page permalink: /about/ title: About Me tags: [about] image: feature: abstract-12.jpg comments: true share: true --- ```js { "name": "hacke2", "description": "", "version": "0.1.0", "keywords": ["Web APP", "JS", "Frontend Developer", "Node.js"], "contact": { "weibo": "@hacke2", "github": "@hacke2", "mail": "hacke2cn@gmail.com" }, "location": "北京", "workAt": "Alibaba Inc." } ``` `任性的`扫一下吧~ ^_^
home
我的支付宝
================================================ FILE: assets/assets/js/plugins/jquery.dlmenu.js ================================================ /** * jquery.dlmenu.js v1.0.1 * http://www.codrops.com * * Licensed under the MIT license. * http://www.opensource.org/licenses/mit-license.php * * Copyright 2013, Codrops * http://www.codrops.com */ ;( function( $, window, undefined ) { 'use strict'; // global var Modernizr = window.Modernizr, $body = $( 'body' ); $.DLMenu = function( options, element ) { this.$el = $( element ); this._init( options ); }; // the options $.DLMenu.defaults = { // classes for the animation effects animationClasses : { classin : 'dl-animate-in-1', classout : 'dl-animate-out-1' }, // callback: click a link that has a sub menu // el is the link element (li); name is the level name onLevelClick : function( el, name ) { return false; }, // callback: click a link that does not have a sub menu // el is the link element (li); ev is the event obj onLinkClick : function( el, ev ) { return false; } }; $.DLMenu.prototype = { _init : function( options ) { // options this.options = $.extend( true, {}, $.DLMenu.defaults, options ); // cache some elements and initialize some variables this._config(); var animEndEventNames = { 'WebkitAnimation' : 'webkitAnimationEnd', 'OAnimation' : 'oAnimationEnd', 'msAnimation' : 'MSAnimationEnd', 'animation' : 'animationend' }, transEndEventNames = { 'WebkitTransition' : 'webkitTransitionEnd', 'MozTransition' : 'transitionend', 'OTransition' : 'oTransitionEnd', 'msTransition' : 'MSTransitionEnd', 'transition' : 'transitionend' }; // animation end event name this.animEndEventName = animEndEventNames[ Modernizr.prefixed( 'animation' ) ] + '.dlmenu'; // transition end event name this.transEndEventName = transEndEventNames[ Modernizr.prefixed( 'transition' ) ] + '.dlmenu', // support for css animations and css transitions this.supportAnimations = Modernizr.cssanimations, this.supportTransitions = Modernizr.csstransitions; this._initEvents(); }, _config : function() { this.open = false; this.$trigger = this.$el.children( '.dl-trigger' ); this.$menu = this.$el.children( 'ul.dl-menu' ); this.$menuitems = this.$menu.find( 'li:not(.dl-back)' ); this.$el.find( 'ul.dl-submenu' ).prepend( '
  • back
  • ' ); this.$back = this.$menu.find( 'li.dl-back' ); }, _initEvents : function() { var self = this; this.$trigger.on( 'click.dlmenu', function() { if( self.open ) { self._closeMenu(); } else { self._openMenu(); } return false; } ); this.$menuitems.on( 'click.dlmenu', function( event ) { event.stopPropagation(); var $item = $(this), $submenu = $item.children( 'ul.dl-submenu' ); if( $submenu.length > 0 ) { var $flyin = $submenu.clone().css({ opacity: 0, margin: 0 }).insertAfter( self.$menu ), onAnimationEndFn = function() { self.$menu.off( self.animEndEventName ).removeClass( self.options.animationClasses.classout ).addClass( 'dl-subview' ); $item.addClass( 'dl-subviewopen' ).parents( '.dl-subviewopen:first' ).removeClass( 'dl-subviewopen' ).addClass( 'dl-subview' ); $flyin.remove(); }; setTimeout( function() { $flyin.addClass( self.options.animationClasses.classin ); self.$menu.addClass( self.options.animationClasses.classout ); if( self.supportAnimations ) { self.$menu.on( self.animEndEventName, onAnimationEndFn ); } else { onAnimationEndFn.call(); } self.options.onLevelClick( $item, $item.children( 'a:first' ).text() ); } ); return false; } else { self.options.onLinkClick( $item, event ); } } ); this.$back.on( 'click.dlmenu', function( event ) { var $this = $( this ), $submenu = $this.parents( 'ul.dl-submenu:first' ), $item = $submenu.parent(), $flyin = $submenu.clone().insertAfter( self.$menu ); var onAnimationEndFn = function() { self.$menu.off( self.animEndEventName ).removeClass( self.options.animationClasses.classin ); $flyin.remove(); }; setTimeout( function() { $flyin.addClass( self.options.animationClasses.classout ); self.$menu.addClass( self.options.animationClasses.classin ); if( self.supportAnimations ) { self.$menu.on( self.animEndEventName, onAnimationEndFn ); } else { onAnimationEndFn.call(); } $item.removeClass( 'dl-subviewopen' ); var $subview = $this.parents( '.dl-subview:first' ); if( $subview.is( 'li' ) ) { $subview.addClass( 'dl-subviewopen' ); } $subview.removeClass( 'dl-subview' ); } ); return false; } ); }, closeMenu : function() { if( this.open ) { this._closeMenu(); } }, _closeMenu : function() { var self = this, onTransitionEndFn = function() { self.$menu.off( self.transEndEventName ); self._resetMenu(); }; this.$menu.removeClass( 'dl-menuopen' ); this.$menu.addClass( 'dl-menu-toggle' ); this.$trigger.removeClass( 'dl-active' ); if( this.supportTransitions ) { this.$menu.on( this.transEndEventName, onTransitionEndFn ); } else { onTransitionEndFn.call(); } this.open = false; }, openMenu : function() { if( !this.open ) { this._openMenu(); } }, _openMenu : function() { var self = this; // clicking somewhere else makes the menu close $body.off( 'click' ).on( 'click.dlmenu', function() { self._closeMenu() ; } ); this.$menu.addClass( 'dl-menuopen dl-menu-toggle' ).on( this.transEndEventName, function() { $( this ).removeClass( 'dl-menu-toggle' ); } ); this.$trigger.addClass( 'dl-active' ); this.open = true; }, // resets the menu to its original state (first level of options) _resetMenu : function() { this.$menu.removeClass( 'dl-subview' ); this.$menuitems.removeClass( 'dl-subview dl-subviewopen' ); } }; var logError = function( message ) { if ( window.console ) { window.console.error( message ); } }; $.fn.dlmenu = function( options ) { if ( typeof options === 'string' ) { var args = Array.prototype.slice.call( arguments, 1 ); this.each(function() { var instance = $.data( this, 'dlmenu' ); if ( !instance ) { logError( "cannot call methods on dlmenu prior to initialization; " + "attempted to call method '" + options + "'" ); return; } if ( !$.isFunction( instance[options] ) || options.charAt(0) === "_" ) { logError( "no such method '" + options + "' for dlmenu instance" ); return; } instance[ options ].apply( instance, args ); }); } else { this.each(function() { var instance = $.data( this, 'dlmenu' ); if ( instance ) { instance._init(); } else { instance = $.data( this, 'dlmenu', new $.DLMenu( options, this ) ); } }); } return this; }; } )( jQuery, window ); ================================================ FILE: assets/assets/js/plugins/jquery.fitvids.js ================================================ /*global jQuery */ /*jshint multistr:true, browser:true */ /*! * FitVids 1.0 * * Copyright 2011, Chris Coyier - http://css-tricks.com + Dave Rupert - http://daverupert.com * Credit to Thierry Koblentz - http://www.alistapart.com/articles/creating-intrinsic-ratios-for-video/ * Released under the WTFPL license - http://sam.zoy.org/wtfpl/ * * Date: Thu Sept 01 18:00:00 2011 -0500 */ (function( $ ){ "use strict"; $.fn.fitVids = function( options ) { var settings = { customSelector: null }; var div = document.createElement('div'), ref = document.getElementsByTagName('base')[0] || document.getElementsByTagName('script')[0]; div.className = 'fit-vids-style'; div.innerHTML = '­'; ref.parentNode.insertBefore(div,ref); if ( options ) { $.extend( settings, options ); } return this.each(function(){ var selectors = [ "iframe[src*='player.vimeo.com']", "iframe[src*='www.youtube.com']", "iframe[src*='www.youtube-nocookie.com']", "iframe[src*='www.kickstarter.com']", "object", "embed" ]; if (settings.customSelector) { selectors.push(settings.customSelector); } var $allVideos = $(this).find(selectors.join(',')); $allVideos.each(function(){ var $this = $(this); if (this.tagName.toLowerCase() === 'embed' && $this.parent('object').length || $this.parent('.fluid-width-video-wrapper').length) { return; } var height = ( this.tagName.toLowerCase() === 'object' || ($this.attr('height') && !isNaN(parseInt($this.attr('height'), 10))) ) ? parseInt($this.attr('height'), 10) : $this.height(), width = !isNaN(parseInt($this.attr('width'), 10)) ? parseInt($this.attr('width'), 10) : $this.width(), aspectRatio = height / width; if(!$this.attr('id')){ var videoID = 'fitvid' + Math.floor(Math.random()*999999); $this.attr('id', videoID); } $this.wrap('
    ').parent('.fluid-width-video-wrapper').css('padding-top', (aspectRatio * 100)+"%"); $this.removeAttr('height').removeAttr('width'); }); }); }; })( jQuery ); ================================================ FILE: assets/assets/js/plugins/jquery.magnific-popup.js ================================================ /*! Magnific Popup - v0.9.3 - 2013-07-16 * http://dimsemenov.com/plugins/magnific-popup/ * Copyright (c) 2013 Dmitry Semenov; */ ;(function($) { /*>>core*/ /** * * Magnific Popup Core JS file * */ /** * Private static constants */ var CLOSE_EVENT = 'Close', BEFORE_CLOSE_EVENT = 'BeforeClose', AFTER_CLOSE_EVENT = 'AfterClose', BEFORE_APPEND_EVENT = 'BeforeAppend', MARKUP_PARSE_EVENT = 'MarkupParse', OPEN_EVENT = 'Open', CHANGE_EVENT = 'Change', NS = 'mfp', EVENT_NS = '.' + NS, READY_CLASS = 'mfp-ready', REMOVING_CLASS = 'mfp-removing', PREVENT_CLOSE_CLASS = 'mfp-prevent-close'; /** * Private vars */ var mfp, // As we have only one instance of MagnificPopup object, we define it locally to not to use 'this' MagnificPopup = function(){}, _isJQ = !!(window.jQuery), _prevStatus, _window = $(window), _body, _document, _prevContentType, _wrapClasses, _currPopupType; /** * Private functions */ var _mfpOn = function(name, f) { mfp.ev.on(NS + name + EVENT_NS, f); }, _getEl = function(className, appendTo, html, raw) { var el = document.createElement('div'); el.className = 'mfp-'+className; if(html) { el.innerHTML = html; } if(!raw) { el = $(el); if(appendTo) { el.appendTo(appendTo); } } else if(appendTo) { appendTo.appendChild(el); } return el; }, _mfpTrigger = function(e, data) { mfp.ev.triggerHandler(NS + e, data); if(mfp.st.callbacks) { // converts "mfpEventName" to "eventName" callback and triggers it if it's present e = e.charAt(0).toLowerCase() + e.slice(1); if(mfp.st.callbacks[e]) { mfp.st.callbacks[e].apply(mfp, $.isArray(data) ? data : [data]); } } }, _setFocus = function() { (mfp.st.focus ? mfp.content.find(mfp.st.focus).eq(0) : mfp.wrap).trigger('focus'); }, _getCloseBtn = function(type) { if(type !== _currPopupType || !mfp.currTemplate.closeBtn) { mfp.currTemplate.closeBtn = $( mfp.st.closeMarkup.replace('%title%', mfp.st.tClose ) ); _currPopupType = type; } return mfp.currTemplate.closeBtn; }, // Initialize Magnific Popup only when called at least once _checkInstance = function() { if(!$.magnificPopup.instance) { mfp = new MagnificPopup(); mfp.init(); $.magnificPopup.instance = mfp; } }, // Check to close popup or not // "target" is an element that was clicked _checkIfClose = function(target) { if($(target).hasClass(PREVENT_CLOSE_CLASS)) { return; } var closeOnContent = mfp.st.closeOnContentClick; var closeOnBg = mfp.st.closeOnBgClick; if(closeOnContent && closeOnBg) { return true; } else { // We close the popup if click is on close button or on preloader. Or if there is no content. if(!mfp.content || $(target).hasClass('mfp-close') || (mfp.preloader && target === mfp.preloader[0]) ) { return true; } // if click is outside the content if( (target !== mfp.content[0] && !$.contains(mfp.content[0], target)) ) { if(closeOnBg) { // last check, if the clicked element is in DOM, (in case it's removed onclick) if( $.contains(document, target) ) { return true; } } } else if(closeOnContent) { return true; } } return false; }, // CSS transition detection, http://stackoverflow.com/questions/7264899/detect-css-transitions-using-javascript-and-without-modernizr supportsTransitions = function() { var s = document.createElement('p').style, // 's' for style. better to create an element if body yet to exist v = ['ms','O','Moz','Webkit']; // 'v' for vendor if( s['transition'] !== undefined ) { return true; } while( v.length ) { if( v.pop() + 'Transition' in s ) { return true; } } return false; }; /** * Public functions */ MagnificPopup.prototype = { constructor: MagnificPopup, /** * Initializes Magnific Popup plugin. * This function is triggered only once when $.fn.magnificPopup or $.magnificPopup is executed */ init: function() { var appVersion = navigator.appVersion; mfp.isIE7 = appVersion.indexOf("MSIE 7.") !== -1; mfp.isIE8 = appVersion.indexOf("MSIE 8.") !== -1; mfp.isLowIE = mfp.isIE7 || mfp.isIE8; mfp.isAndroid = (/android/gi).test(appVersion); mfp.isIOS = (/iphone|ipad|ipod/gi).test(appVersion); mfp.supportsTransition = supportsTransitions(); // We disable fixed positioned lightbox on devices that don't handle it nicely. // If you know a better way of detecting this - let me know. mfp.probablyMobile = (mfp.isAndroid || mfp.isIOS || /(Opera Mini)|Kindle|webOS|BlackBerry|(Opera Mobi)|(Windows Phone)|IEMobile/i.test(navigator.userAgent) ); _body = $(document.body); _document = $(document); mfp.popupsCache = {}; }, /** * Opens popup * @param data [description] */ open: function(data) { var i; if(data.isObj === false) { // convert jQuery collection to array to avoid conflicts later mfp.items = data.items.toArray(); mfp.index = 0; var items = data.items, item; for(i = 0; i < items.length; i++) { item = items[i]; if(item.parsed) { item = item.el[0]; } if(item === data.el[0]) { mfp.index = i; break; } } } else { mfp.items = $.isArray(data.items) ? data.items : [data.items]; mfp.index = data.index || 0; } // if popup is already opened - we just update the content if(mfp.isOpen) { mfp.updateItemHTML(); return; } mfp.types = []; _wrapClasses = ''; if(data.mainEl && data.mainEl.length) { mfp.ev = data.mainEl.eq(0); } else { mfp.ev = _document; } if(data.key) { if(!mfp.popupsCache[data.key]) { mfp.popupsCache[data.key] = {}; } mfp.currTemplate = mfp.popupsCache[data.key]; } else { mfp.currTemplate = {}; } mfp.st = $.extend(true, {}, $.magnificPopup.defaults, data ); mfp.fixedContentPos = mfp.st.fixedContentPos === 'auto' ? !mfp.probablyMobile : mfp.st.fixedContentPos; if(mfp.st.modal) { mfp.st.closeOnContentClick = false; mfp.st.closeOnBgClick = false; mfp.st.showCloseBtn = false; mfp.st.enableEscapeKey = false; } // Building markup // main containers are created only once if(!mfp.bgOverlay) { // Dark overlay mfp.bgOverlay = _getEl('bg').on('click'+EVENT_NS, function() { mfp.close(); }); mfp.wrap = _getEl('wrap').attr('tabindex', -1).on('click'+EVENT_NS, function(e) { if(_checkIfClose(e.target)) { mfp.close(); } }); mfp.container = _getEl('container', mfp.wrap); } mfp.contentContainer = _getEl('content'); if(mfp.st.preloader) { mfp.preloader = _getEl('preloader', mfp.container, mfp.st.tLoading); } // Initializing modules var modules = $.magnificPopup.modules; for(i = 0; i < modules.length; i++) { var n = modules[i]; n = n.charAt(0).toUpperCase() + n.slice(1); mfp['init'+n].call(mfp); } _mfpTrigger('BeforeOpen'); if(mfp.st.showCloseBtn) { // Close button if(!mfp.st.closeBtnInside) { mfp.wrap.append( _getCloseBtn() ); } else { _mfpOn(MARKUP_PARSE_EVENT, function(e, template, values, item) { values.close_replaceWith = _getCloseBtn(item.type); }); _wrapClasses += ' mfp-close-btn-in'; } } if(mfp.st.alignTop) { _wrapClasses += ' mfp-align-top'; } if(mfp.fixedContentPos) { mfp.wrap.css({ overflow: mfp.st.overflowY, overflowX: 'hidden', overflowY: mfp.st.overflowY }); } else { mfp.wrap.css({ top: _window.scrollTop(), position: 'absolute' }); } if( mfp.st.fixedBgPos === false || (mfp.st.fixedBgPos === 'auto' && !mfp.fixedContentPos) ) { mfp.bgOverlay.css({ height: _document.height(), position: 'absolute' }); } if(mfp.st.enableEscapeKey) { // Close on ESC key _document.on('keyup' + EVENT_NS, function(e) { if(e.keyCode === 27) { mfp.close(); } }); } _window.on('resize' + EVENT_NS, function() { mfp.updateSize(); }); if(!mfp.st.closeOnContentClick) { _wrapClasses += ' mfp-auto-cursor'; } if(_wrapClasses) mfp.wrap.addClass(_wrapClasses); // this triggers recalculation of layout, so we get it once to not to trigger twice var windowHeight = mfp.wH = _window.height(); var windowStyles = {}; if( mfp.fixedContentPos ) { if(mfp._hasScrollBar(windowHeight)){ var s = mfp._getScrollbarSize(); if(s) { windowStyles.paddingRight = s; } } } if(mfp.fixedContentPos) { if(!mfp.isIE7) { windowStyles.overflow = 'hidden'; } else { // ie7 double-scroll bug $('body, html').css('overflow', 'hidden'); } } var classesToadd = mfp.st.mainClass; if(mfp.isIE7) { classesToadd += ' mfp-ie7'; } if(classesToadd) { mfp._addClassToMFP( classesToadd ); } // add content mfp.updateItemHTML(); _mfpTrigger('BuildControls'); // remove scrollbar, add padding e.t.c $('html').css(windowStyles); // add everything to DOM mfp.bgOverlay.add(mfp.wrap).prependTo( document.body ); // Save last focused element mfp._lastFocusedEl = document.activeElement; // Wait for next cycle to allow CSS transition setTimeout(function() { if(mfp.content) { mfp._addClassToMFP(READY_CLASS); _setFocus(); } else { // if content is not defined (not loaded e.t.c) we add class only for BG mfp.bgOverlay.addClass(READY_CLASS); } // Trap the focus in popup _document.on('focusin' + EVENT_NS, function (e) { if( e.target !== mfp.wrap[0] && !$.contains(mfp.wrap[0], e.target) ) { _setFocus(); return false; } }); }, 16); mfp.isOpen = true; mfp.updateSize(windowHeight); _mfpTrigger(OPEN_EVENT); }, /** * Closes the popup */ close: function() { if(!mfp.isOpen) return; _mfpTrigger(BEFORE_CLOSE_EVENT); mfp.isOpen = false; // for CSS3 animation if(mfp.st.removalDelay && !mfp.isLowIE && mfp.supportsTransition ) { mfp._addClassToMFP(REMOVING_CLASS); setTimeout(function() { mfp._close(); }, mfp.st.removalDelay); } else { mfp._close(); } }, /** * Helper for close() function */ _close: function() { _mfpTrigger(CLOSE_EVENT); var classesToRemove = REMOVING_CLASS + ' ' + READY_CLASS + ' '; mfp.bgOverlay.detach(); mfp.wrap.detach(); mfp.container.empty(); if(mfp.st.mainClass) { classesToRemove += mfp.st.mainClass + ' '; } mfp._removeClassFromMFP(classesToRemove); if(mfp.fixedContentPos) { var windowStyles = {paddingRight: ''}; if(mfp.isIE7) { $('body, html').css('overflow', ''); } else { windowStyles.overflow = ''; } $('html').css(windowStyles); } _document.off('keyup' + EVENT_NS + ' focusin' + EVENT_NS); mfp.ev.off(EVENT_NS); // clean up DOM elements that aren't removed mfp.wrap.attr('class', 'mfp-wrap').removeAttr('style'); mfp.bgOverlay.attr('class', 'mfp-bg'); mfp.container.attr('class', 'mfp-container'); // remove close button from target element if(mfp.st.showCloseBtn && (!mfp.st.closeBtnInside || mfp.currTemplate[mfp.currItem.type] === true)) { if(mfp.currTemplate.closeBtn) mfp.currTemplate.closeBtn.detach(); } if(mfp._lastFocusedEl) { $(mfp._lastFocusedEl).trigger('focus'); // put tab focus back } mfp.currItem = null; mfp.content = null; mfp.currTemplate = null; mfp.prevHeight = 0; _mfpTrigger(AFTER_CLOSE_EVENT); }, updateSize: function(winHeight) { if(mfp.isIOS) { // fixes iOS nav bars https://github.com/dimsemenov/Magnific-Popup/issues/2 var zoomLevel = document.documentElement.clientWidth / window.innerWidth; var height = window.innerHeight * zoomLevel; mfp.wrap.css('height', height); mfp.wH = height; } else { mfp.wH = winHeight || _window.height(); } // Fixes #84: popup incorrectly positioned with position:relative on body if(!mfp.fixedContentPos) { mfp.wrap.css('height', mfp.wH); } _mfpTrigger('Resize'); }, /** * Set content of popup based on current index */ updateItemHTML: function() { var item = mfp.items[mfp.index]; // Detach and perform modifications mfp.contentContainer.detach(); if(mfp.content) mfp.content.detach(); if(!item.parsed) { item = mfp.parseEl( mfp.index ); } var type = item.type; _mfpTrigger('BeforeChange', [mfp.currItem ? mfp.currItem.type : '', type]); // BeforeChange event works like so: // _mfpOn('BeforeChange', function(e, prevType, newType) { }); mfp.currItem = item; if(!mfp.currTemplate[type]) { var markup = mfp.st[type] ? mfp.st[type].markup : false; // allows to modify markup _mfpTrigger('FirstMarkupParse', markup); if(markup) { mfp.currTemplate[type] = $(markup); } else { // if there is no markup found we just define that template is parsed mfp.currTemplate[type] = true; } } if(_prevContentType && _prevContentType !== item.type) { mfp.container.removeClass('mfp-'+_prevContentType+'-holder'); } var newContent = mfp['get' + type.charAt(0).toUpperCase() + type.slice(1)](item, mfp.currTemplate[type]); mfp.appendContent(newContent, type); item.preloaded = true; _mfpTrigger(CHANGE_EVENT, item); _prevContentType = item.type; // Append container back after its content changed mfp.container.prepend(mfp.contentContainer); _mfpTrigger('AfterChange'); }, /** * Set HTML content of popup */ appendContent: function(newContent, type) { mfp.content = newContent; if(newContent) { if(mfp.st.showCloseBtn && mfp.st.closeBtnInside && mfp.currTemplate[type] === true) { // if there is no markup, we just append close button element inside if(!mfp.content.find('.mfp-close').length) { mfp.content.append(_getCloseBtn()); } } else { mfp.content = newContent; } } else { mfp.content = ''; } _mfpTrigger(BEFORE_APPEND_EVENT); mfp.container.addClass('mfp-'+type+'-holder'); mfp.contentContainer.append(mfp.content); }, /** * Creates Magnific Popup data object based on given data * @param {int} index Index of item to parse */ parseEl: function(index) { var item = mfp.items[index], type = item.type; if(item.tagName) { item = { el: $(item) }; } else { item = { data: item, src: item.src }; } if(item.el) { var types = mfp.types; // check for 'mfp-TYPE' class for(var i = 0; i < types.length; i++) { if( item.el.hasClass('mfp-'+types[i]) ) { type = types[i]; break; } } item.src = item.el.attr('data-mfp-src'); if(!item.src) { item.src = item.el.attr('href'); } } item.type = type || mfp.st.type || 'inline'; item.index = index; item.parsed = true; mfp.items[index] = item; _mfpTrigger('ElementParse', item); return mfp.items[index]; }, /** * Initializes single popup or a group of popups */ addGroup: function(el, options) { var eHandler = function(e) { e.mfpEl = this; mfp._openClick(e, el, options); }; if(!options) { options = {}; } var eName = 'click.magnificPopup'; options.mainEl = el; if(options.items) { options.isObj = true; el.off(eName).on(eName, eHandler); } else { options.isObj = false; if(options.delegate) { el.off(eName).on(eName, options.delegate , eHandler); } else { options.items = el; el.off(eName).on(eName, eHandler); } } }, _openClick: function(e, el, options) { var midClick = options.midClick !== undefined ? options.midClick : $.magnificPopup.defaults.midClick; if(!midClick && ( e.which === 2 || e.ctrlKey || e.metaKey ) ) { return; } var disableOn = options.disableOn !== undefined ? options.disableOn : $.magnificPopup.defaults.disableOn; if(disableOn) { if($.isFunction(disableOn)) { if( !disableOn.call(mfp) ) { return true; } } else { // else it's number if( _window.width() < disableOn ) { return true; } } } if(e.type) { e.preventDefault(); // This will prevent popup from closing if element is inside and popup is already opened if(mfp.isOpen) { e.stopPropagation(); } } options.el = $(e.mfpEl); if(options.delegate) { options.items = el.find(options.delegate); } mfp.open(options); }, /** * Updates text on preloader */ updateStatus: function(status, text) { if(mfp.preloader) { if(_prevStatus !== status) { mfp.container.removeClass('mfp-s-'+_prevStatus); } if(!text && status === 'loading') { text = mfp.st.tLoading; } var data = { status: status, text: text }; // allows to modify status _mfpTrigger('UpdateStatus', data); status = data.status; text = data.text; mfp.preloader.html(text); mfp.preloader.find('a').on('click', function(e) { e.stopImmediatePropagation(); }); mfp.container.addClass('mfp-s-'+status); _prevStatus = status; } }, /* "Private" helpers that aren't private at all */ _addClassToMFP: function(cName) { mfp.bgOverlay.addClass(cName); mfp.wrap.addClass(cName); }, _removeClassFromMFP: function(cName) { this.bgOverlay.removeClass(cName); mfp.wrap.removeClass(cName); }, _hasScrollBar: function(winHeight) { return ( (mfp.isIE7 ? _document.height() : document.body.scrollHeight) > (winHeight || _window.height()) ); }, _parseMarkup: function(template, values, item) { var arr; if(item.data) { values = $.extend(item.data, values); } _mfpTrigger(MARKUP_PARSE_EVENT, [template, values, item] ); $.each(values, function(key, value) { if(value === undefined || value === false) { return true; } arr = key.split('_'); if(arr.length > 1) { var el = template.find(EVENT_NS + '-'+arr[0]); if(el.length > 0) { var attr = arr[1]; if(attr === 'replaceWith') { if(el[0] !== value[0]) { el.replaceWith(value); } } else if(attr === 'img') { if(el.is('img')) { el.attr('src', value); } else { el.replaceWith( '' ); } } else { el.attr(arr[1], value); } } } else { template.find(EVENT_NS + '-'+key).html(value); } }); }, _getScrollbarSize: function() { // thx David if(mfp.scrollbarSize === undefined) { var scrollDiv = document.createElement("div"); scrollDiv.id = "mfp-sbm"; scrollDiv.style.cssText = 'width: 99px; height: 99px; overflow: scroll; position: absolute; top: -9999px;'; document.body.appendChild(scrollDiv); mfp.scrollbarSize = scrollDiv.offsetWidth - scrollDiv.clientWidth; document.body.removeChild(scrollDiv); } return mfp.scrollbarSize; } }; /* MagnificPopup core prototype end */ /** * Public static functions */ $.magnificPopup = { instance: null, proto: MagnificPopup.prototype, modules: [], open: function(options, index) { _checkInstance(); if(!options) options = {}; options.isObj = true; options.index = index || 0; return this.instance.open(options); }, close: function() { return $.magnificPopup.instance.close(); }, registerModule: function(name, module) { if(module.options) { $.magnificPopup.defaults[name] = module.options; } $.extend(this.proto, module.proto); this.modules.push(name); }, defaults: { // Info about options is in docs: // http://dimsemenov.com/plugins/magnific-popup/documentation.html#options disableOn: 0, key: null, midClick: false, mainClass: '', preloader: true, focus: '', // CSS selector of input to focus after popup is opened closeOnContentClick: false, closeOnBgClick: true, closeBtnInside: true, showCloseBtn: true, enableEscapeKey: true, modal: false, alignTop: false, removalDelay: 0, fixedContentPos: 'auto', fixedBgPos: 'auto', overflowY: 'auto', closeMarkup: '', tClose: 'Close (Esc)', tLoading: 'Loading...' } }; $.fn.magnificPopup = function(options) { _checkInstance(); var jqEl = $(this); // We call some API method of first param is a string if (typeof options === "string" ) { if(options === 'open') { var items, itemOpts = _isJQ ? jqEl.data('magnificPopup') : jqEl[0].magnificPopup, index = parseInt(arguments[1], 10) || 0; if(itemOpts.items) { items = itemOpts.items[index]; } else { items = jqEl; if(itemOpts.delegate) { items = items.find(itemOpts.delegate); } items = items.eq( index ); } mfp._openClick({mfpEl:items}, jqEl, itemOpts); } else { if(mfp.isOpen) mfp[options].apply(mfp, Array.prototype.slice.call(arguments, 1)); } } else { /* * As Zepto doesn't support .data() method for objects * and it works only in normal browsers * we assign "options" object directly to the DOM element. FTW! */ if(_isJQ) { jqEl.data('magnificPopup', options); } else { jqEl[0].magnificPopup = options; } mfp.addGroup(jqEl, options); } return jqEl; }; //Quick benchmark /* var start = performance.now(), i, rounds = 1000; for(i = 0; i < rounds; i++) { } console.log('Test #1:', performance.now() - start); start = performance.now(); for(i = 0; i < rounds; i++) { } console.log('Test #2:', performance.now() - start); */ /*>>core*/ /*>>inline*/ var INLINE_NS = 'inline', _hiddenClass, _inlinePlaceholder, _lastInlineElement, _putInlineElementsBack = function() { if(_lastInlineElement) { _inlinePlaceholder.after( _lastInlineElement.addClass(_hiddenClass) ).detach(); _lastInlineElement = null; } }; $.magnificPopup.registerModule(INLINE_NS, { options: { hiddenClass: 'hide', // will be appended with `mfp-` prefix markup: '', tNotFound: 'Content not found' }, proto: { initInline: function() { mfp.types.push(INLINE_NS); _mfpOn(CLOSE_EVENT+'.'+INLINE_NS, function() { _putInlineElementsBack(); }); }, getInline: function(item, template) { _putInlineElementsBack(); if(item.src) { var inlineSt = mfp.st.inline, el = $(item.src); if(el.length) { // If target element has parent - we replace it with placeholder and put it back after popup is closed var parent = el[0].parentNode; if(parent && parent.tagName) { if(!_inlinePlaceholder) { _hiddenClass = inlineSt.hiddenClass; _inlinePlaceholder = _getEl(_hiddenClass); _hiddenClass = 'mfp-'+_hiddenClass; } // replace target inline element with placeholder _lastInlineElement = el.after(_inlinePlaceholder).detach().removeClass(_hiddenClass); } mfp.updateStatus('ready'); } else { mfp.updateStatus('error', inlineSt.tNotFound); el = $('
    '); } item.inlineElement = el; return el; } mfp.updateStatus('ready'); mfp._parseMarkup(template, {}, item); return template; } } }); /*>>inline*/ /*>>ajax*/ var AJAX_NS = 'ajax', _ajaxCur, _removeAjaxCursor = function() { if(_ajaxCur) { _body.removeClass(_ajaxCur); } }; $.magnificPopup.registerModule(AJAX_NS, { options: { settings: null, cursor: 'mfp-ajax-cur', tError: 'The content could not be loaded.' }, proto: { initAjax: function() { mfp.types.push(AJAX_NS); _ajaxCur = mfp.st.ajax.cursor; _mfpOn(CLOSE_EVENT+'.'+AJAX_NS, function() { _removeAjaxCursor(); if(mfp.req) { mfp.req.abort(); } }); }, getAjax: function(item) { if(_ajaxCur) _body.addClass(_ajaxCur); mfp.updateStatus('loading'); var opts = $.extend({ url: item.src, success: function(data, textStatus, jqXHR) { var temp = { data:data, xhr:jqXHR }; _mfpTrigger('ParseAjax', temp); mfp.appendContent( $(temp.data), AJAX_NS ); item.finished = true; _removeAjaxCursor(); _setFocus(); setTimeout(function() { mfp.wrap.addClass(READY_CLASS); }, 16); mfp.updateStatus('ready'); _mfpTrigger('AjaxContentAdded'); }, error: function() { _removeAjaxCursor(); item.finished = item.loadError = true; mfp.updateStatus('error', mfp.st.ajax.tError.replace('%url%', item.src)); } }, mfp.st.ajax.settings); mfp.req = $.ajax(opts); return ''; } } }); /*>>ajax*/ /*>>image*/ var _imgInterval, _getTitle = function(item) { if(item.data && item.data.title !== undefined) return item.data.title; var src = mfp.st.image.titleSrc; if(src) { if($.isFunction(src)) { return src.call(mfp, item); } else if(item.el) { return item.el.attr(src) || ''; } } return ''; }; $.magnificPopup.registerModule('image', { options: { markup: '
    '+ '
    '+ '
    '+ '
    '+ '
    '+ '
    '+ '
    '+ '
    ', cursor: 'mfp-zoom-out-cur', titleSrc: 'title', verticalFit: true, tError: 'The image could not be loaded.' }, proto: { initImage: function() { var imgSt = mfp.st.image, ns = '.image'; mfp.types.push('image'); _mfpOn(OPEN_EVENT+ns, function() { if(mfp.currItem.type === 'image' && imgSt.cursor) { _body.addClass(imgSt.cursor); } }); _mfpOn(CLOSE_EVENT+ns, function() { if(imgSt.cursor) { _body.removeClass(imgSt.cursor); } _window.off('resize' + EVENT_NS); }); _mfpOn('Resize'+ns, mfp.resizeImage); if(mfp.isLowIE) { _mfpOn('AfterChange', mfp.resizeImage); } }, resizeImage: function() { var item = mfp.currItem; if(!item.img) return; if(mfp.st.image.verticalFit) { var decr = 0; // fix box-sizing in ie7/8 if(mfp.isLowIE) { decr = parseInt(item.img.css('padding-top'), 10) + parseInt(item.img.css('padding-bottom'),10); } item.img.css('max-height', mfp.wH-decr); } }, _onImageHasSize: function(item) { if(item.img) { item.hasSize = true; if(_imgInterval) { clearInterval(_imgInterval); } item.isCheckingImgSize = false; _mfpTrigger('ImageHasSize', item); if(item.imgHidden) { if(mfp.content) mfp.content.removeClass('mfp-loading'); item.imgHidden = false; } } }, /** * Function that loops until the image has size to display elements that rely on it asap */ findImageSize: function(item) { var counter = 0, img = item.img[0], mfpSetInterval = function(delay) { if(_imgInterval) { clearInterval(_imgInterval); } // decelerating interval that checks for size of an image _imgInterval = setInterval(function() { if(img.naturalWidth > 0) { mfp._onImageHasSize(item); return; } if(counter > 200) { clearInterval(_imgInterval); } counter++; if(counter === 3) { mfpSetInterval(10); } else if(counter === 40) { mfpSetInterval(50); } else if(counter === 100) { mfpSetInterval(500); } }, delay); }; mfpSetInterval(1); }, getImage: function(item, template) { var guard = 0, // image load complete handler onLoadComplete = function() { if(item) { if (item.img[0].complete) { item.img.off('.mfploader'); if(item === mfp.currItem){ mfp._onImageHasSize(item); mfp.updateStatus('ready'); } item.hasSize = true; item.loaded = true; _mfpTrigger('ImageLoadComplete'); } else { // if image complete check fails 200 times (20 sec), we assume that there was an error. guard++; if(guard < 200) { setTimeout(onLoadComplete,100); } else { onLoadError(); } } } }, // image error handler onLoadError = function() { if(item) { item.img.off('.mfploader'); if(item === mfp.currItem){ mfp._onImageHasSize(item); mfp.updateStatus('error', imgSt.tError.replace('%url%', item.src) ); } item.hasSize = true; item.loaded = true; item.loadError = true; } }, imgSt = mfp.st.image; var el = template.find('.mfp-img'); if(el.length) { var img = new Image(); img.className = 'mfp-img'; item.img = $(img).on('load.mfploader', onLoadComplete).on('error.mfploader', onLoadError); img.src = item.src; // without clone() "error" event is not firing when IMG is replaced by new IMG // TODO: find a way to avoid such cloning if(el.is('img')) { item.img = item.img.clone(); } if(item.img[0].naturalWidth > 0) { item.hasSize = true; } } mfp._parseMarkup(template, { title: _getTitle(item), img_replaceWith: item.img }, item); mfp.resizeImage(); if(item.hasSize) { if(_imgInterval) clearInterval(_imgInterval); if(item.loadError) { template.addClass('mfp-loading'); mfp.updateStatus('error', imgSt.tError.replace('%url%', item.src) ); } else { template.removeClass('mfp-loading'); mfp.updateStatus('ready'); } return template; } mfp.updateStatus('loading'); item.loading = true; if(!item.hasSize) { item.imgHidden = true; template.addClass('mfp-loading'); mfp.findImageSize(item); } return template; } } }); /*>>image*/ /*>>zoom*/ var hasMozTransform, getHasMozTransform = function() { if(hasMozTransform === undefined) { hasMozTransform = document.createElement('p').style.MozTransform !== undefined; } return hasMozTransform; }; $.magnificPopup.registerModule('zoom', { options: { enabled: false, easing: 'ease-in-out', duration: 300, opener: function(element) { return element.is('img') ? element : element.find('img'); } }, proto: { initZoom: function() { var zoomSt = mfp.st.zoom, ns = '.zoom'; if(!zoomSt.enabled || !mfp.supportsTransition) { return; } var duration = zoomSt.duration, getElToAnimate = function(image) { var newImg = image.clone().removeAttr('style').removeAttr('class').addClass('mfp-animated-image'), transition = 'all '+(zoomSt.duration/1000)+'s ' + zoomSt.easing, cssObj = { position: 'fixed', zIndex: 9999, left: 0, top: 0, '-webkit-backface-visibility': 'hidden' }, t = 'transition'; cssObj['-webkit-'+t] = cssObj['-moz-'+t] = cssObj['-o-'+t] = cssObj[t] = transition; newImg.css(cssObj); return newImg; }, showMainContent = function() { mfp.content.css('visibility', 'visible'); }, openTimeout, animatedImg; _mfpOn('BuildControls'+ns, function() { if(mfp._allowZoom()) { clearTimeout(openTimeout); mfp.content.css('visibility', 'hidden'); // Basically, all code below does is clones existing image, puts in on top of the current one and animated it image = mfp._getItemToZoom(); if(!image) { showMainContent(); return; } animatedImg = getElToAnimate(image); animatedImg.css( mfp._getOffset() ); mfp.wrap.append(animatedImg); openTimeout = setTimeout(function() { animatedImg.css( mfp._getOffset( true ) ); openTimeout = setTimeout(function() { showMainContent(); setTimeout(function() { animatedImg.remove(); image = animatedImg = null; _mfpTrigger('ZoomAnimationEnded'); }, 16); // avoid blink when switching images }, duration); // this timeout equals animation duration }, 16); // by adding this timeout we avoid short glitch at the beginning of animation // Lots of timeouts... } }); _mfpOn(BEFORE_CLOSE_EVENT+ns, function() { if(mfp._allowZoom()) { clearTimeout(openTimeout); mfp.st.removalDelay = duration; if(!image) { image = mfp._getItemToZoom(); if(!image) { return; } animatedImg = getElToAnimate(image); } animatedImg.css( mfp._getOffset(true) ); mfp.wrap.append(animatedImg); mfp.content.css('visibility', 'hidden'); setTimeout(function() { animatedImg.css( mfp._getOffset() ); }, 16); } }); _mfpOn(CLOSE_EVENT+ns, function() { if(mfp._allowZoom()) { showMainContent(); if(animatedImg) { animatedImg.remove(); } } }); }, _allowZoom: function() { return mfp.currItem.type === 'image'; }, _getItemToZoom: function() { if(mfp.currItem.hasSize) { return mfp.currItem.img; } else { return false; } }, // Get element postion relative to viewport _getOffset: function(isLarge) { var el; if(isLarge) { el = mfp.currItem.img; } else { el = mfp.st.zoom.opener(mfp.currItem.el || mfp.currItem); } var offset = el.offset(); var paddingTop = parseInt(el.css('padding-top'),10); var paddingBottom = parseInt(el.css('padding-bottom'),10); offset.top -= ( $(window).scrollTop() - paddingTop ); /* Animating left + top + width/height looks glitchy in Firefox, but perfect in Chrome. And vice-versa. */ var obj = { width: el.width(), // fix Zepto height+padding issue height: (_isJQ ? el.innerHeight() : el[0].offsetHeight) - paddingBottom - paddingTop }; // I hate to do this, but there is no another option if( getHasMozTransform() ) { obj['-moz-transform'] = obj['transform'] = 'translate(' + offset.left + 'px,' + offset.top + 'px)'; } else { obj.left = offset.left; obj.top = offset.top; } return obj; } } }); /*>>zoom*/ /*>>iframe*/ var IFRAME_NS = 'iframe', _emptyPage = '//about:blank', _fixIframeBugs = function(isShowing) { if(mfp.currTemplate[IFRAME_NS]) { var el = mfp.currTemplate[IFRAME_NS].find('iframe'); if(el.length) { // reset src after the popup is closed to avoid "video keeps playing after popup is closed" bug if(!isShowing) { el[0].src = _emptyPage; } // IE8 black screen bug fix if(mfp.isIE8) { el.css('display', isShowing ? 'block' : 'none'); } } } }; $.magnificPopup.registerModule(IFRAME_NS, { options: { markup: '
    '+ '
    '+ ''+ '
    ', srcAction: 'iframe_src', // we don't care and support only one default type of URL by default patterns: { youtube: { index: 'youtube.com', id: 'v=', src: '//www.youtube.com/embed/%id%?autoplay=1' }, vimeo: { index: 'vimeo.com/', id: '/', src: '//player.vimeo.com/video/%id%?autoplay=1' }, gmaps: { index: '//maps.google.', src: '%id%&output=embed' } } }, proto: { initIframe: function() { mfp.types.push(IFRAME_NS); _mfpOn('BeforeChange', function(e, prevType, newType) { if(prevType !== newType) { if(prevType === IFRAME_NS) { _fixIframeBugs(); // iframe if removed } else if(newType === IFRAME_NS) { _fixIframeBugs(true); // iframe is showing } }// else { // iframe source is switched, don't do anything //} }); _mfpOn(CLOSE_EVENT + '.' + IFRAME_NS, function() { _fixIframeBugs(); }); }, getIframe: function(item, template) { var embedSrc = item.src; var iframeSt = mfp.st.iframe; $.each(iframeSt.patterns, function() { if(embedSrc.indexOf( this.index ) > -1) { if(this.id) { if(typeof this.id === 'string') { embedSrc = embedSrc.substr(embedSrc.lastIndexOf(this.id)+this.id.length, embedSrc.length); } else { embedSrc = this.id.call( this, embedSrc ); } } embedSrc = this.src.replace('%id%', embedSrc ); return false; // break; } }); var dataObj = {}; if(iframeSt.srcAction) { dataObj[iframeSt.srcAction] = embedSrc; } mfp._parseMarkup(template, dataObj, item); mfp.updateStatus('ready'); return template; } } }); /*>>iframe*/ /*>>gallery*/ /** * Get looped index depending on number of slides */ var _getLoopedId = function(index) { var numSlides = mfp.items.length; if(index > numSlides - 1) { return index - numSlides; } else if(index < 0) { return numSlides + index; } return index; }, _replaceCurrTotal = function(text, curr, total) { return text.replace('%curr%', curr + 1).replace('%total%', total); }; $.magnificPopup.registerModule('gallery', { options: { enabled: false, arrowMarkup: '', preload: [0,2], navigateByImgClick: true, arrows: true, tPrev: 'Previous (Left arrow key)', tNext: 'Next (Right arrow key)', tCounter: '%curr% of %total%' }, proto: { initGallery: function() { var gSt = mfp.st.gallery, ns = '.mfp-gallery', supportsFastClick = Boolean($.fn.mfpFastClick); mfp.direction = true; // true - next, false - prev if(!gSt || !gSt.enabled ) return false; _wrapClasses += ' mfp-gallery'; _mfpOn(OPEN_EVENT+ns, function() { if(gSt.navigateByImgClick) { mfp.wrap.on('click'+ns, '.mfp-img', function() { if(mfp.items.length > 1) { mfp.next(); return false; } }); } _document.on('keydown'+ns, function(e) { if (e.keyCode === 37) { mfp.prev(); } else if (e.keyCode === 39) { mfp.next(); } }); }); _mfpOn('UpdateStatus'+ns, function(e, data) { if(data.text) { data.text = _replaceCurrTotal(data.text, mfp.currItem.index, mfp.items.length); } }); _mfpOn(MARKUP_PARSE_EVENT+ns, function(e, element, values, item) { var l = mfp.items.length; values.counter = l > 1 ? _replaceCurrTotal(gSt.tCounter, item.index, l) : ''; }); _mfpOn('BuildControls' + ns, function() { if(mfp.items.length > 1 && gSt.arrows && !mfp.arrowLeft) { var markup = gSt.arrowMarkup, arrowLeft = mfp.arrowLeft = $( markup.replace('%title%', gSt.tPrev).replace('%dir%', 'left') ).addClass(PREVENT_CLOSE_CLASS), arrowRight = mfp.arrowRight = $( markup.replace('%title%', gSt.tNext).replace('%dir%', 'right') ).addClass(PREVENT_CLOSE_CLASS); var eName = supportsFastClick ? 'mfpFastClick' : 'click'; arrowLeft[eName](function() { mfp.prev(); }); arrowRight[eName](function() { mfp.next(); }); // Polyfill for :before and :after (adds elements with classes mfp-a and mfp-b) if(mfp.isIE7) { _getEl('b', arrowLeft[0], false, true); _getEl('a', arrowLeft[0], false, true); _getEl('b', arrowRight[0], false, true); _getEl('a', arrowRight[0], false, true); } mfp.container.append(arrowLeft.add(arrowRight)); } }); _mfpOn(CHANGE_EVENT+ns, function() { if(mfp._preloadTimeout) clearTimeout(mfp._preloadTimeout); mfp._preloadTimeout = setTimeout(function() { mfp.preloadNearbyImages(); mfp._preloadTimeout = null; }, 16); }); _mfpOn(CLOSE_EVENT+ns, function() { _document.off(ns); mfp.wrap.off('click'+ns); if(mfp.arrowLeft && supportsFastClick) { mfp.arrowLeft.add(mfp.arrowRight).destroyMfpFastClick(); } mfp.arrowRight = mfp.arrowLeft = null; }); }, next: function() { mfp.direction = true; mfp.index = _getLoopedId(mfp.index + 1); mfp.updateItemHTML(); }, prev: function() { mfp.direction = false; mfp.index = _getLoopedId(mfp.index - 1); mfp.updateItemHTML(); }, goTo: function(newIndex) { mfp.direction = (newIndex >= mfp.index); mfp.index = newIndex; mfp.updateItemHTML(); }, preloadNearbyImages: function() { var p = mfp.st.gallery.preload, preloadBefore = Math.min(p[0], mfp.items.length), preloadAfter = Math.min(p[1], mfp.items.length), i; for(i = 1; i <= (mfp.direction ? preloadAfter : preloadBefore); i++) { mfp._preloadItem(mfp.index+i); } for(i = 1; i <= (mfp.direction ? preloadBefore : preloadAfter); i++) { mfp._preloadItem(mfp.index-i); } }, _preloadItem: function(index) { index = _getLoopedId(index); if(mfp.items[index].preloaded) { return; } var item = mfp.items[index]; if(!item.parsed) { item = mfp.parseEl( index ); } _mfpTrigger('LazyLoad', item); if(item.type === 'image') { item.img = $('').on('load.mfploader', function() { item.hasSize = true; }).on('error.mfploader', function() { item.hasSize = true; item.loadError = true; _mfpTrigger('LazyLoadError', item); }).attr('src', item.src); } item.preloaded = true; } } }); /* Touch Support that might be implemented some day addSwipeGesture: function() { var startX, moved, multipleTouches; return; var namespace = '.mfp', addEventNames = function(pref, down, move, up, cancel) { mfp._tStart = pref + down + namespace; mfp._tMove = pref + move + namespace; mfp._tEnd = pref + up + namespace; mfp._tCancel = pref + cancel + namespace; }; if(window.navigator.msPointerEnabled) { addEventNames('MSPointer', 'Down', 'Move', 'Up', 'Cancel'); } else if('ontouchstart' in window) { addEventNames('touch', 'start', 'move', 'end', 'cancel'); } else { return; } _window.on(mfp._tStart, function(e) { var oE = e.originalEvent; multipleTouches = moved = false; startX = oE.pageX || oE.changedTouches[0].pageX; }).on(mfp._tMove, function(e) { if(e.originalEvent.touches.length > 1) { multipleTouches = e.originalEvent.touches.length; } else { //e.preventDefault(); moved = true; } }).on(mfp._tEnd + ' ' + mfp._tCancel, function(e) { if(moved && !multipleTouches) { var oE = e.originalEvent, diff = startX - (oE.pageX || oE.changedTouches[0].pageX); if(diff > 20) { mfp.next(); } else if(diff < -20) { mfp.prev(); } } }); }, */ /*>>gallery*/ /*>>retina*/ var RETINA_NS = 'retina'; $.magnificPopup.registerModule(RETINA_NS, { options: { replaceSrc: function(item) { return item.src.replace(/\.\w+$/, function(m) { return '@2x' + m; }); }, ratio: 1 // Function or number. Set to 1 to disable. }, proto: { initRetina: function() { if(window.devicePixelRatio > 1) { var st = mfp.st.retina, ratio = st.ratio; ratio = !isNaN(ratio) ? ratio : ratio(); if(ratio > 1) { _mfpOn('ImageHasSize' + '.' + RETINA_NS, function(e, item) { item.img.css({ 'max-width': item.img[0].naturalWidth / ratio, 'width': '100%' }); }); _mfpOn('ElementParse' + '.' + RETINA_NS, function(e, item) { item.src = st.replaceSrc(item, ratio); }); } } } } }); /*>>retina*/ /*>>fastclick*/ /** * FastClick event implementation. (removes 300ms delay on touch devices) * Based on https://developers.google.com/mobile/articles/fast_buttons * * You may use it outside the Magnific Popup by calling just: * * $('.your-el').mfpFastClick(function() { * console.log('Clicked!'); * }); * * To unbind: * $('.your-el').destroyMfpFastClick(); * * * Note that it's a very basic and simple implementation, it blocks ghost click on the same element where it was bound. * If you need something more advanced, use plugin by FT Labs https://github.com/ftlabs/fastclick * */ (function() { var ghostClickDelay = 1000, supportsTouch = 'ontouchstart' in window, unbindTouchMove = function() { _window.off('touchmove'+ns+' touchend'+ns); }, eName = 'mfpFastClick', ns = '.'+eName; // As Zepto.js doesn't have an easy way to add custom events (like jQuery), so we implement it in this way $.fn.mfpFastClick = function(callback) { return $(this).each(function() { var elem = $(this), lock; if( supportsTouch ) { var timeout, startX, startY, pointerMoved, point, numPointers; elem.on('touchstart' + ns, function(e) { pointerMoved = false; numPointers = 1; point = e.originalEvent ? e.originalEvent.touches[0] : e.touches[0]; startX = point.clientX; startY = point.clientY; _window.on('touchmove'+ns, function(e) { point = e.originalEvent ? e.originalEvent.touches : e.touches; numPointers = point.length; point = point[0]; if (Math.abs(point.clientX - startX) > 10 || Math.abs(point.clientY - startY) > 10) { pointerMoved = true; unbindTouchMove(); } }).on('touchend'+ns, function(e) { unbindTouchMove(); if(pointerMoved || numPointers > 1) { return; } lock = true; e.preventDefault(); clearTimeout(timeout); timeout = setTimeout(function() { lock = false; }, ghostClickDelay); callback(); }); }); } elem.on('click' + ns, function() { if(!lock) { callback(); } }); }); }; $.fn.destroyMfpFastClick = function() { $(this).off('touchstart' + ns + ' click' + ns); if(supportsTouch) _window.off('touchmove'+ns+' touchend'+ns); }; })(); /*>>fastclick*/ })(window.jQuery || window.Zepto); ================================================ FILE: assets/css/main.css ================================================ /*! // =========================================================== // HPSTR Jekyll Theme // By: Michael Rose // =========================================================== */ *, *:after, *:before { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; } audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; } audio:not([controls]) { display: none; } html { font-size: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } a:focus { outline: thin dotted #333; outline: 5px auto -webkit-focus-ring-color; outline-offset: -2px; } a:hover, a:active { outline: 0; } sub, sup { position: relative; font-size: 75%; line-height: 0; vertical-align: baseline; } sup { top: -0.5em; } sub { bottom: -0.25em; } blockquote { margin: 0; } img { width: auto\9; height: auto; max-width: 100%; vertical-align: middle; border: 0; -ms-interpolation-mode: bicubic; } #map_canvas img, .google-maps img { max-width: none; } button, input, select, textarea { margin: 0; font-size: 100%; vertical-align: middle; } button, input { *overflow: visible; line-height: normal; } button::-moz-focus-inner, input::-moz-focus-inner { padding: 0; border: 0; } button, html input[type="button"], input[type="reset"], input[type="submit"] { cursor: pointer; -webkit-appearance: button; } label, select, button, input[type="button"], input[type="reset"], input[type="submit"], input[type="radio"], input[type="checkbox"] { cursor: pointer; } input[type="search"] { -webkit-box-sizing: content-box; -moz-box-sizing: content-box; -ms-box-sizing: content-box; box-sizing: content-box; -webkit-appearance: textfield; } input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; } textarea { overflow: auto; vertical-align: top; } @media print { * { color: #000 !important; text-shadow: none !important; background: transparent !important; box-shadow: none !important; } a, a:visited { text-decoration: underline; } a[href]:after { content: " (" attr(href) ")"; } abbr[title]:after { content: " (" attr(title) ")"; } .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } thead { display: table-header-group; } tr, img { page-break-inside: avoid; } img { max-width: 100% !important; } @page { margin: 0.5cm; } p, h2, h3 { orphans: 3; widows: 3; } h2, h3 { page-break-after: avoid; } } body { margin-top: 26px; font-size: 16px; } ::-moz-selection { color: #222222; text-shadow: none; background-color: #c8c8c8; } ::selection { color: #222222; text-shadow: none; background-color: #c8c8c8; } .wrap { margin: 0 auto; } .all-caps { text-transform: uppercase; } .pull-left { float: left; } .pull-right { float: right; } .unstyled-list { padding-left: 0; margin-left: 0; list-style: none; } .unstyled-list li { list-style-type: none; } .inline-list { padding-left: 0; margin-left: 0; list-style: none; } .inline-list li { display: inline; list-style-type: none; } b, i, strong, em, blockquote, p, q, span, figure, img, h1, h2, header, input, a { -webkit-transition: all 0.2s ease; -moz-transition: all 0.2s ease; -ms-transition: all 0.2s ease; -o-transition: all 0.2s ease; transition: all 0.2s ease; } body { font-family: 'Lato', Calibri, Arial, sans-serif; color: #222222; } h1, h2, h3, h4, h5, h6 { font-family: 'Lato', Calibri, Arial, sans-serif; } h1 { font-size: 32px; font-size: 2rem; } a { color: #222222; text-decoration: none; } a:visited { color: #555555; } a:hover { color: #000000; } a:focus { color: #000000; outline: thin dotted; } a:hover, a:active { outline: 0; } .link-arrow { font-style: normal; font-weight: 100; text-decoration: underline; } figcaption { padding-top: 10px; font-size: 14px; font-size: 0.875rem; line-height: 1.8571; line-height: 1.3; color: #3c3c3c; } .notice { padding: .5em 1em; margin-top: 1.5em; font-size: 14px; font-size: 0.875rem; text-indent: 0; background-color: #e8e8e8; border: 1px solid #b5b5b5; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } blockquote { padding-left: 20px; font-family: serif; font-style: italic; border-left: 8px solid #bbbbbb; } @media only screen and (min-width: 48em) { blockquote { margin-left: -28px; } } .entry-content .footnotes ol, .entry-content .footnotes li, .entry-content .footnotes p { margin-bottom: 26px; margin-bottom: 1.625rem; font-size: 14px; font-size: 0.875rem; line-height: 1.8571; } tt, code, kbd, samp, pre { font-family: monospace; } p code, li code { padding: 0 5px; margin: 0 2px; font-size: 12px; font-size: 0.75rem; line-height: 1.5; white-space: nowrap; background-color: #f2f2f2; border: 1px solid #e6e6e6; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } pre { overflow-x: auto; font-size: 12px; font-size: 0.75rem; line-height: 1.5; } pre::-webkit-scrollbar { height: 12px; background-color: #34362e; border-radius: 0 0 4px 4px; } pre::-webkit-scrollbar-thumb:horizontal { background-color: #6a6d5d; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; } .highlight { margin-bottom: 1.5em; font-size: 12px; font-size: 0.75rem; line-height: 2.1667; color: #d0d0d0; background-color: #272822; border: 1px solid #dbdbdb; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } .highlight pre { position: relative; padding: 1em; margin: 0; } .highlight .lineno { padding-right: 24px; color: #8f908a; } .highlight .hll { background-color: #49483e; } .highlight .c { color: #75715e; } .highlight .err { color: #960050; background-color: #1e0010; } .highlight .k { color: #66d9ef; } .highlight .l { color: #ae81ff; } .highlight .n { color: #f8f8f2; } .highlight .o { color: #f92672; } .highlight .p { color: #f8f8f2; } .highlight .cm { color: #75715e; } .highlight .cp { color: #75715e; } .highlight .c1 { color: #75715e; } .highlight .cs { color: #75715e; } .highlight .ge { font-style: italic; } .highlight .gs { font-weight: bold; } .highlight .kc { color: #66d9ef; } .highlight .kd { color: #66d9ef; } .highlight .kn { color: #f92672; } .highlight .kp { color: #66d9ef; } .highlight .kr { color: #66d9ef; } .highlight .kt { color: #66d9ef; } .highlight .ld { color: #e6db74; } .highlight .m { color: #ae81ff; } .highlight .s { color: #e6db74; } .highlight .na { color: #a6e22e; } .highlight .nb { color: #f8f8f2; } .highlight .nc { color: #a6e22e; } .highlight .no { color: #66d9ef; } .highlight .nd { color: #a6e22e; } .highlight .ni { color: #f8f8f2; } .highlight .ne { color: #a6e22e; } .highlight .nf { color: #a6e22e; } .highlight .nl { color: #f8f8f2; } .highlight .nn { color: #f8f8f2; } .highlight .nx { color: #a6e22e; } .highlight .py { color: #f8f8f2; } .highlight .nt { color: #f92672; } .highlight .nv { color: #f8f8f2; } .highlight .ow { color: #f92672; } .highlight .w { color: #f8f8f2; } .highlight .mf { color: #ae81ff; } .highlight .mh { color: #ae81ff; } .highlight .mi { color: #ae81ff; } .highlight .mo { color: #ae81ff; } .highlight .sb { color: #e6db74; } .highlight .sc { color: #e6db74; } .highlight .sd { color: #e6db74; } .highlight .s2 { color: #e6db74; } .highlight .se { color: #ae81ff; } .highlight .sh { color: #e6db74; } .highlight .si { color: #e6db74; } .highlight .sx { color: #e6db74; } .highlight .sr { color: #e6db74; } .highlight .s1 { color: #e6db74; } .highlight .ss { color: #e6db74; } .highlight .bp { color: #f8f8f2; } .highlight .vc { color: #f8f8f2; } .highlight .vg { color: #f8f8f2; } .highlight .vi { color: #f8f8f2; } .highlight .il { color: #ae81ff; } .CodeRay { margin-bottom: 1.5em; font-family: monospace; font-size: 12px; font-size: 0.75rem; line-height: 2.1667; color: #d0d0d0; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } .CodeRay .code pre { padding: 1em; margin: 0; background-color: #272822; border: 1px solid #dbdbdb; } span.CodeRay { padding: 2px; white-space: pre; border: 0; } table.CodeRay { width: 100%; padding: 2px; border-collapse: collapse; } table.CodeRay td { padding: 1em 0.5em; vertical-align: top; } .CodeRay .line-numbers, .CodeRay .no { color: #8f908a; text-align: right; } .CodeRay .line-numbers a { color: #8f908a; } .CodeRay .line-numbers tt { font-weight: bold; } .CodeRay .line-numbers .highlighted { color: #ff0000; } .CodeRay .line { display: block; float: left; width: 100%; } .CodeRay span.line-numbers { padding: 0 24px 0 4px; } .CodeRay .code { width: 100%; } ol.CodeRay { font-size: 10pt; } ol.CodeRay li { white-space: pre; } .CodeRay .code pre { overflow: auto; } .CodeRay .debug { color: white ! important; background: blue ! important; } .CodeRay .doctype, .CodeRay .key, .CodeRay .instance-variable { color: #f8f8f2; } .CodeRay .attribute-name { color: #a6e22e; } .CodeRay .symbol, .CodeRay .integer, .CodeRay .float { color: #ff658b; } .CodeRay .string { color: #2dc900; } .CodeRay .keyword { color: #66d9ef; } .CodeRay .function, .CodeRay .class { color: #a6e22e; } .CodeRay .regexp, .CodeRay .constant, .CodeRay .tag { color: #f92672; } .CodeRay .modifier, .CodeRay .predefined-constant { color: #ff84e4; } .CodeRay .comment { color: #75715e; } .CodeRay .error { color: #ecc; } .CodeRay .content { color: #e6db74; } .CodeRay .delimiter { color: #e6db74; } .CodeRay .inline { color: #e6db74; } .gist .highlight { color: #000; } .gist .render-container .render-viewer-error, .gist .render-container .render-viewer-fatal, .gist .render-container .octospinner { display: none; } .gist .gist-render iframe { width: 100%; } .gist .gist-file.gist-render .highlight { border: none; } .gist .gist-file .gist-meta .highlight a { font-weight: 700; color: #666; text-decoration: none; } .gist .highlight { background: #fff; } .gist .highlight .err { color: #a61717; background-color: #e3d2d2; } .gist .highlight .cp { font-weight: 700; color: #999; } .gist .highlight .cs { font-style: italic; font-weight: 700; color: #999; } .gist .highlight .gd { color: #000; background-color: #fdd; } .gist .highlight .gd .x { color: #000; background-color: #faa; } .gist .highlight .ge { font-style: italic; color: #000; } .gist .highlight .gi { color: #000; background-color: #dfd; } .gist .highlight .gi .x { color: #000; background-color: #afa; } .gist .highlight .go { color: #888; } .gist .highlight .gs { font-weight: 700; } .gist .highlight .gu { color: #aaa; } .gist .highlight .nb { color: #0086b3; } .gist .highlight .ni { color: purple; } .gist .highlight .nt { color: navy; } .gist .highlight .w { color: #bbb; } .gist .highlight .sr { color: #009926; } .gist .highlight .ss { color: #990073; } .gist .highlight .c, .gist .highlight .cm, .gist .highlight .c1 { font-style: italic; color: #998; } .gist .highlight .k, .gist .highlight .o, .gist .highlight .kc, .gist .highlight .kd, .gist .highlight .kp, .gist .highlight .kr, .gist .highlight .ow, .gist .highlight .n, .gist .highlight .p { font-weight: 700; color: #000; } .gist .highlight .gr, .gist .highlight .gt { color: #a00; } .gist .highlight .gh, .gist .highlight .bp { color: #999; } .gist .highlight .gp, .gist .highlight .nn { color: #555; } .gist .highlight .kt, .gist .highlight .nc { font-weight: 700; color: #458; } .gist .highlight .m, .gist .highlight .mf, .gist .highlight .mh, .gist .highlight .mi, .gist .highlight .mo, .gist .highlight .il { color: #099; } .gist .highlight .s, .gist .highlight .sb, .gist .highlight .sc, .gist .highlight .sd, .gist .highlight .s2, .gist .highlight .se, .gist .highlight .sh, .gist .highlight .si, .gist .highlight .sx, .gist .highlight .s1 { color: #d14; } .gist .highlight .na, .gist .highlight .no, .gist .highlight .nv, .gist .highlight .vc, .gist .highlight .vg, .gist .highlight .vi { color: teal; } .gist .highlight .ne, .gist .highlight .nf { font-weight: 700; color: #900; } .clearfix { *zoom: 1; } .clearfix:before, .clearfix:after { display: table; line-height: 0; content: ""; } .clearfix:after { clear: both; } .hidden { display: none; visibility: hidden; } .visuallyhidden.focusable:active, .visuallyhidden.focusable:focus { position: static; width: auto; height: auto; margin: 0; overflow: visible; clip: auto; } hr { display: block; height: 1px; padding: 0; margin: 1em 0; border: 0; border-top: 1px solid #ccc; border-bottom: 1px solid #fff; } figure { padding-top: 10px; padding-bottom: 10px; margin: 0; *zoom: 1; } figure:before, figure:after { display: table; line-height: 0; content: ""; } figure:after { clear: both; } figure img { margin-bottom: 10px; } figure a img { -webkit-transform: translate(0, 0); -moz-transform: translate(0, 0); -ms-transform: translate(0, 0); -o-transform: translate(0, 0); transform: translate(0, 0); -webkit-transition-duration: 0.25s; -moz-transition-duration: 0.25s; -o-transition-duration: 0.25s; } figure a img:hover { -webkit-transform: translate(0, -5px); -moz-transform: translate(0, -5px); -ms-transform: translate(0, -5px); -o-transform: translate(0, -5px); transform: translate(0, -5px); -webkit-box-shadow: 0 0 10px rgba(34, 34, 34, 0.2); -moz-box-shadow: 0 0 10px rgba(34, 34, 34, 0.2); box-shadow: 0 0 10px rgba(34, 34, 34, 0.2); } @media only screen and (min-width: 62.5em) { figure.half img { float: left; width: 310px; margin-right: 10px; } figure.half figcaption { clear: left; } } @media only screen and (min-width: 62.5em) { figure.third img { float: left; width: 200px; margin-right: 10px; } figure.third figcaption { clear: left; } } svg:not(:root) { overflow: hidden; } .btn { display: inline-block; padding: 8px 20px; margin-bottom: 20px; font-size: 14px; font-size: 0.875rem; color: #ffffff; background-color: #222222; border-color: #222222; border-style: solid !important; border-width: 2px !important; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } .btn:visited { color: #ffffff; } .btn:hover { color: #222222; background-color: #ffffff; } .btn-success { color: #ffffff; background-color: #5cb85c; border-color: #5cb85c; } .btn-success:visited { color: #ffffff; } .btn-success:hover { color: #5cb85c; background-color: #ffffff; } .btn-warning { color: #ffffff; background-color: #dd8338; border-color: #dd8338; } .btn-warning:visited { color: #ffffff; } .btn-warning:hover { color: #dd8338; background-color: #ffffff; } .btn-danger { color: #ffffff; background-color: #c64537; border-color: #c64537; } .btn-danger:visited { color: #ffffff; } .btn-danger:hover { color: #c64537; background-color: #ffffff; } .btn-info { color: #ffffff; background-color: #308cbc; border-color: #308cbc; } .btn-info:visited { color: #ffffff; } .btn-info:hover { color: #308cbc; background-color: #ffffff; } .well { padding: 20px; border: 1px solid #222222; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; } .animated { -webkit-animation-duration: 1s; -moz-animation-duration: 1s; -ms-animation-duration: 1s; -o-animation-duration: 1s; animation-duration: 1s; -webkit-animation-fill-mode: both; -moz-animation-fill-mode: both; -ms-animation-fill-mode: both; -o-animation-fill-mode: both; animation-fill-mode: both; } .animated.hinge { -webkit-animation-duration: 2s; -moz-animation-duration: 2s; -ms-animation-duration: 2s; -o-animation-duration: 2s; animation-duration: 2s; } @-webkit-keyframes fadeIn { 0% { opacity: 0; } 100% { opacity: 1; } } @-moz-keyframes fadeIn { 0% { opacity: 0; } 100% { opacity: 1; } } @-o-keyframes fadeIn { 0% { opacity: 0; } 100% { opacity: 1; } } @keyframes fadeIn { 0% { opacity: 0; } 100% { opacity: 1; } } .fadeIn { -webkit-animation-name: fadeIn; -moz-animation-name: fadeIn; -o-animation-name: fadeIn; animation-name: fadeIn; } @-webkit-keyframes fadeInDown { 0% { opacity: 0; -webkit-transform: translateY(-20px); } 100% { opacity: 1; -webkit-transform: translateY(0); } } @-moz-keyframes fadeInDown { 0% { opacity: 0; -moz-transform: translateY(-20px); } 100% { opacity: 1; -moz-transform: translateY(0); } } @-o-keyframes fadeInDown { 0% { opacity: 0; -o-transform: translateY(-20px); } 100% { opacity: 1; -o-transform: translateY(0); } } @keyframes fadeInDown { 0% { opacity: 0; transform: translateY(-20px); } 100% { opacity: 1; transform: translateY(0); } } .fadeInDown { -webkit-animation-name: fadeInDown; -moz-animation-name: fadeInDown; -o-animation-name: fadeInDown; animation-name: fadeInDown; } @-webkit-keyframes fadeInDownBig { 0% { opacity: 0; -webkit-transform: translateY(-2000px); } 100% { opacity: 1; -webkit-transform: translateY(0); } } @-moz-keyframes fadeInDownBig { 0% { opacity: 0; -moz-transform: translateY(-2000px); } 100% { opacity: 1; -moz-transform: translateY(0); } } @-o-keyframes fadeInDownBig { 0% { opacity: 0; -o-transform: translateY(-2000px); } 100% { opacity: 1; -o-transform: translateY(0); } } @keyframes fadeInDownBig { 0% { opacity: 0; transform: translateY(-2000px); } 100% { opacity: 1; transform: translateY(0); } } .fadeInDownBig { -webkit-animation-name: fadeInDownBig; -moz-animation-name: fadeInDownBig; -o-animation-name: fadeInDownBig; animation-name: fadeInDownBig; } @-webkit-keyframes bounceIn { 0% { opacity: 0; -webkit-transform: scale(0.3); } 50% { opacity: 1; -webkit-transform: scale(1.05); } 70% { -webkit-transform: scale(0.9); } 100% { -webkit-transform: scale(1); } } @-moz-keyframes bounceIn { 0% { opacity: 0; -moz-transform: scale(0.3); } 50% { opacity: 1; -moz-transform: scale(1.05); } 70% { -moz-transform: scale(0.9); } 100% { -moz-transform: scale(1); } } @-o-keyframes bounceIn { 0% { opacity: 0; -o-transform: scale(0.3); } 50% { opacity: 1; -o-transform: scale(1.05); } 70% { -o-transform: scale(0.9); } 100% { -o-transform: scale(1); } } @keyframes bounceIn { 0% { opacity: 0; transform: scale(0.3); } 50% { opacity: 1; transform: scale(1.05); } 70% { transform: scale(0.9); } 100% { transform: scale(1); } } .bounceIn { -webkit-animation-name: bounceIn; -moz-animation-name: bounceIn; -o-animation-name: bounceIn; animation-name: bounceIn; } @-webkit-keyframes bounceInDown { 0% { opacity: 0; -webkit-transform: translateY(-2000px); } 60% { opacity: 1; -webkit-transform: translateY(30px); } 80% { -webkit-transform: translateY(-10px); } 100% { -webkit-transform: translateY(0); } } @-moz-keyframes bounceInDown { 0% { opacity: 0; -moz-transform: translateY(-2000px); } 60% { opacity: 1; -moz-transform: translateY(30px); } 80% { -moz-transform: translateY(-10px); } 100% { -moz-transform: translateY(0); } } @-o-keyframes bounceInDown { 0% { opacity: 0; -o-transform: translateY(-2000px); } 60% { opacity: 1; -o-transform: translateY(30px); } 80% { -o-transform: translateY(-10px); } 100% { -o-transform: translateY(0); } } @keyframes bounceInDown { 0% { opacity: 0; transform: translateY(-2000px); } 60% { opacity: 1; transform: translateY(30px); } 80% { transform: translateY(-10px); } 100% { transform: translateY(0); } } .bounceInDown { -webkit-animation-name: bounceInDown; -moz-animation-name: bounceInDown; -o-animation-name: bounceInDown; animation-name: bounceInDown; } @-webkit-keyframes drop { 0% { -webkit-transform: translateY(-500px); } 100% { -webkit-transform: translateY(0); } } @-moz-keyframes drop { 0% { -moz-transform: translateY(-500px); } 100% { -moz-transform: translateY(0); } } @-o-keyframes drop { 0% { -o-transform: translateY(-500px); } 100% { -o-transform: translateY(0); } } @keyframes drop { 0% { transform: translateY(-500px); } 100% { transform: translateY(0); } } .drop { -webkit-animation-name: drop; -moz-animation-name: drop; -o-animation-name: drop; animation-name: drop; } .dl-menuwrapper { position: absolute; top: 0; left: 0; z-index: 1000; width: 100%; -webkit-perspective: 1000px; -moz-perspective: 1000px; perspective: 1000px; -webkit-perspective-origin: 50% 200%; -moz-perspective-origin: 50% 200%; perspective-origin: 50% 200%; } @media only screen and (min-width: 48em) { .dl-menuwrapper { position: fixed; top: 25px; left: 25px; max-width: 175px; } } .dl-menuwrapper button { position: relative; top: 0; left: 0; width: 48px; height: 45px; overflow: hidden; text-indent: -900em; cursor: pointer; background: #222222; border: none; -webkit-border-top-right-radius: 0; border-top-right-radius: 0; -webkit-border-bottom-right-radius: 3px; border-bottom-right-radius: 3px; -webkit-border-bottom-left-radius: 0; border-bottom-left-radius: 0; -webkit-border-top-left-radius: 0; border-top-left-radius: 0; -moz-border-radius-topright: 0; -moz-border-radius-bottomright: 3px; -moz-border-radius-bottomleft: 0; -moz-border-radius-topleft: 0; outline: none; opacity: 0.6; -webkit-background-clip: padding-box; -moz-background-clip: padding; background-clip: padding-box; } @media only screen and (min-width: 48em) { .dl-menuwrapper button { -webkit-border-top-right-radius: 3px; border-top-right-radius: 3px; -webkit-border-bottom-right-radius: 3px; border-bottom-right-radius: 3px; -webkit-border-bottom-left-radius: 3px; border-bottom-left-radius: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-topright: 3px; -moz-border-radius-bottomright: 3px; -moz-border-radius-bottomleft: 3px; -moz-border-radius-topleft: 3px; -webkit-background-clip: padding-box; -moz-background-clip: padding; background-clip: padding-box; } } .dl-menuwrapper button:hover, .dl-menuwrapper button.dl-active, .dl-menuwrapper ul { background: #aaa; } .dl-menuwrapper button:after { position: absolute; top: 10px; left: 16%; width: 68%; height: 5px; background: #ffffff; content: ''; box-shadow: 0 10px 0 #ffffff, 0 20px 0 #ffffff; } .dl-menuwrapper button.dl-active { display: none; } .dl-menuwrapper ul { padding: 0; list-style: none; -webkit-transform-style: preserve-3d; -moz-transform-style: preserve-3d; transform-style: preserve-3d; } .dl-menuwrapper li { position: relative; } .dl-menuwrapper li h4 { padding: 15px 20px 0; margin: 0; color: rgba(255, 255, 255, 0.9); } .dl-menuwrapper li p { padding: 15px 20px; margin: 0; font-size: 14px; font-size: 0.875rem; color: rgba(255, 255, 255, 0.8); } .dl-menuwrapper li p a { display: inline; padding: 0; font-size: 14px; font-size: 0.875rem; } .dl-menuwrapper li i { display: inline-block; width: 2em; } .dl-menuwrapper li a { position: relative; display: block; padding: 15px 20px; font-size: 14px; font-size: 0.875rem; font-weight: 300; line-height: 20px; color: #ffffff; outline: none; } .dl-menuwrapper li.dl-back > a { padding-left: 30px; background: rgba(0, 0, 0, 0.2); } .dl-menuwrapper li.dl-back:after, .dl-menuwrapper li > a:not(:only-child):after { position: absolute; top: 0; font-family: 'fontawesome'; -webkit-font-smoothing: antialiased; line-height: 50px; color: #ffffff; content: "\f105"; speak: none; } .dl-menuwrapper li.dl-back:after { left: 10px; color: rgba(212, 204, 198, 0.5); -webkit-transform: rotate(180deg); -moz-transform: rotate(180deg); transform: rotate(180deg); } .dl-menuwrapper li > a:after { right: 10px; color: rgba(0, 0, 0, 0.15); } .dl-menuwrapper .dl-menu { position: absolute; width: 100%; max-width: 400px; max-height: 600px; margin: 0; overflow-y: auto; pointer-events: none; opacity: 0; -webkit-transform: translateY(10px); -moz-transform: translateY(10px); transform: translateY(10px); box-shadow: 0 12px 24px rgba(0, 0, 0, 0.35); -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; backface-visibility: hidden; } @media only screen and (min-width: 48em) { .dl-menuwrapper .dl-menu { max-height: 650px; -webkit-border-top-right-radius: 3px; border-top-right-radius: 3px; -webkit-border-bottom-right-radius: 3px; border-bottom-right-radius: 3px; -webkit-border-bottom-left-radius: 3px; border-bottom-left-radius: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-topright: 3px; -moz-border-radius-bottomright: 3px; -moz-border-radius-bottomleft: 3px; -moz-border-radius-topleft: 3px; -webkit-background-clip: padding-box; -moz-background-clip: padding; background-clip: padding-box; } } .dl-menuwrapper .dl-menu.dl-menu-toggle { -webkit-transition: all 0.3s ease; -moz-transition: all 0.3s ease; transition: all 0.3s ease; } .dl-menuwrapper .dl-menu.dl-menuopen { pointer-events: auto; opacity: 1; -webkit-transform: translateY(0); -moz-transform: translateY(0); transform: translateY(0); } .dl-menuwrapper .dl-submenu { -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; box-shadow: 0 12px 24px rgba(0, 0, 0, 0.35); } .dl-menuwrapper li .dl-submenu { display: none; } /* When a submenu is openend, we will hide all li siblings. For that we give a class to the parent menu called "dl-subview". We also hide the submenu link. The opened submenu will get the class "dl-subviewopen". All this is done for any sub-level being entered. */ .dl-menu.dl-subview li, .dl-menu.dl-subview li.dl-subviewopen > a, .dl-menu.dl-subview li.dl-subview > a { display: none; } .dl-menu.dl-subview li.dl-subview, .dl-menu.dl-subview li.dl-subview .dl-submenu, .dl-menu.dl-subview li.dl-subviewopen, .dl-menu.dl-subview li.dl-subviewopen > .dl-submenu, .dl-menu.dl-subview li.dl-subviewopen > .dl-submenu > li { display: block; } /* Animation classes for moving out and in */ .dl-menu.dl-animate-out { -webkit-animation: MenuAnimOut 0.4s ease; -moz-animation: MenuAnimOut 0.4s ease; animation: MenuAnimOut 0.4s ease; } @-webkit-keyframes MenuAnimOut { 100% { opacity: 0; -webkit-transform: translateZ(300px); } } @-moz-keyframes MenuAnimOut { 100% { opacity: 0; -moz-transform: translateZ(300px); } } @keyframes MenuAnimOut { 100% { opacity: 0; transform: translateZ(300px); } } .dl-menu.dl-animate-in { -webkit-animation: MenuAnimIn 0.4s ease; -moz-animation: MenuAnimIn 0.4s ease; animation: MenuAnimIn 0.4s ease; } @-webkit-keyframes MenuAnimIn { 0% { opacity: 0; -webkit-transform: translateZ(300px); } 100% { opacity: 1; -webkit-transform: translateZ(0); } } @-moz-keyframes MenuAnimIn { 0% { opacity: 0; -moz-transform: translateZ(300px); } 100% { opacity: 1; -moz-transform: translateZ(0); } } @keyframes MenuAnimIn { 0% { opacity: 0; transform: translateZ(300px); } 100% { opacity: 1; transform: translateZ(0); } } .dl-menuwrapper > .dl-submenu.dl-animate-in { -webkit-animation: SubMenuAnimIn 0.4s ease; -moz-animation: SubMenuAnimIn 0.4s ease; animation: SubMenuAnimIn 0.4s ease; } @-webkit-keyframes SubMenuAnimIn { 0% { opacity: 0; -webkit-transform: translateZ(-300px); } 100% { opacity: 1; -webkit-transform: translateZ(0); } } @-moz-keyframes SubMenuAnimIn { 0% { opacity: 0; -moz-transform: translateZ(-300px); } 100% { opacity: 1; -moz-transform: translateZ(0); } } @keyframes SubMenuAnimIn { 0% { opacity: 0; transform: translateZ(-300px); } 100% { opacity: 1; transform: translateZ(0); } } .dl-menuwrapper > .dl-submenu.dl-animate-out { -webkit-animation: SubMenuAnimOut 0.4s ease; -moz-animation: SubMenuAnimOut 0.4s ease; animation: SubMenuAnimOut 0.4s ease; } @-webkit-keyframes SubMenuAnimOut { 0% { opacity: 1; -webkit-transform: translateZ(0); } 100% { opacity: 0; -webkit-transform: translateZ(-300px); } } @-moz-keyframes SubMenuAnimOut { 0% { opacity: 1; -moz-transform: translateZ(0); } 100% { opacity: 0; -moz-transform: translateZ(-300px); } } @keyframes SubMenuAnimOut { 0% { opacity: 1; transform: translateZ(0); } 100% { opacity: 0; transform: translateZ(-300px); } } /* No Touch Fallback */ .no-touch .dl-menuwrapper li a:hover { background: rgba(255, 248, 213, 0.1); } /* No JS Fallback */ .no-js .dl-trigger { display: none; } .no-js .dl-menuwrapper .dl-menu { position: relative; pointer-events: auto; opacity: 1; -webkit-transform: none; -moz-transform: none; transform: none; } .no-js .dl-menuwrapper li .dl-submenu { display: block; } .no-js .dl-menuwrapper li.dl-back { display: none; } .no-js .dl-menuwrapper li > a:not(:only-child) { background: rgba(0, 0, 0, 0.1); } .no-js .dl-menuwrapper li > a:not(:only-child):after { content: ''; } .dl-menuwrapper button:hover, .dl-menuwrapper button.dl-active, .dl-menuwrapper ul { background: #222222; } .dl-menu li { display: none; } .dl-menuopen li { display: block; } /*! * Font Awesome 4.1.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) */ /* FONT PATH * -------------------------- */ @font-face { font-family: 'FontAwesome'; font-style: normal; font-weight: normal; src: url('//netdna.bootstrapcdn.com/font-awesome/4.1.0/fonts/fontawesome-webfont.eot?v=4.1.0'); src: url('//netdna.bootstrapcdn.com/font-awesome/4.1.0/fonts/fontawesome-webfont.eot?#iefix&v=4.1.0') format('embedded-opentype'), url('//netdna.bootstrapcdn.com/font-awesome/4.1.0/fonts/fontawesome-webfont.woff?v=4.1.0') format('woff'), url('//netdna.bootstrapcdn.com/font-awesome/4.1.0/fonts/fontawesome-webfont.ttf?v=4.1.0') format('truetype'), url('//netdna.bootstrapcdn.com/font-awesome/4.1.0/fonts/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular') format('svg'); } .fa { display: inline-block; font-family: FontAwesome; -webkit-font-smoothing: antialiased; font-style: normal; font-weight: normal; line-height: 1; -moz-osx-font-smoothing: grayscale; } /* makes the font 33% larger relative to the icon container */ .fa-lg { font-size: 1.3333333333333333em; line-height: 0.75em; vertical-align: -15%; } .fa-2x { font-size: 2em; } .fa-3x { font-size: 3em; } .fa-4x { font-size: 4em; } .fa-5x { font-size: 5em; } .fa-fw { width: 1.2857142857142858em; text-align: center; } .fa-ul { padding-left: 0; margin-left: 2.142857142857143em; list-style-type: none; } .fa-ul > li { position: relative; } .fa-li { position: absolute; top: 0.14285714285714285em; left: -2.142857142857143em; width: 2.142857142857143em; text-align: center; } .fa-li.fa-lg { left: -1.8571428571428572em; } .fa-border { padding: .2em .25em .15em; border: solid 0.08em #eeeeee; border-radius: .1em; } .pull-right { float: right; } .pull-left { float: left; } .fa.pull-left { margin-right: .3em; } .fa.pull-right { margin-left: .3em; } .fa-spin { -webkit-animation: spin 2s infinite linear; -moz-animation: spin 2s infinite linear; -o-animation: spin 2s infinite linear; animation: spin 2s infinite linear; } @-moz-keyframes spin { 0% { -moz-transform: rotate(0deg); } 100% { -moz-transform: rotate(359deg); } } @-webkit-keyframes spin { 0% { -webkit-transform: rotate(0deg); } 100% { -webkit-transform: rotate(359deg); } } @-o-keyframes spin { 0% { -o-transform: rotate(0deg); } 100% { -o-transform: rotate(359deg); } } @keyframes spin { 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { -webkit-transform: rotate(359deg); transform: rotate(359deg); } } .fa-rotate-90 { filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); -webkit-transform: rotate(90deg); -moz-transform: rotate(90deg); -ms-transform: rotate(90deg); -o-transform: rotate(90deg); transform: rotate(90deg); } .fa-rotate-180 { filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); -webkit-transform: rotate(180deg); -moz-transform: rotate(180deg); -ms-transform: rotate(180deg); -o-transform: rotate(180deg); transform: rotate(180deg); } .fa-rotate-270 { filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); -webkit-transform: rotate(270deg); -moz-transform: rotate(270deg); -ms-transform: rotate(270deg); -o-transform: rotate(270deg); transform: rotate(270deg); } .fa-flip-horizontal { filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1); -webkit-transform: scale(-1, 1); -moz-transform: scale(-1, 1); -ms-transform: scale(-1, 1); -o-transform: scale(-1, 1); transform: scale(-1, 1); } .fa-flip-vertical { filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1); -webkit-transform: scale(1, -1); -moz-transform: scale(1, -1); -ms-transform: scale(1, -1); -o-transform: scale(1, -1); transform: scale(1, -1); } .fa-stack { position: relative; display: inline-block; width: 2em; height: 2em; line-height: 2em; vertical-align: middle; } .fa-stack-1x, .fa-stack-2x { position: absolute; left: 0; width: 100%; text-align: center; } .fa-stack-1x { line-height: inherit; } .fa-stack-2x { font-size: 2em; } .fa-inverse { color: #ffffff; } /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen readers do not read off random characters that represent icons */ .fa-glass:before { content: "\f000"; } .fa-music:before { content: "\f001"; } .fa-search:before { content: "\f002"; } .fa-envelope-o:before { content: "\f003"; } .fa-heart:before { content: "\f004"; } .fa-star:before { content: "\f005"; } .fa-star-o:before { content: "\f006"; } .fa-user:before { content: "\f007"; } .fa-film:before { content: "\f008"; } .fa-th-large:before { content: "\f009"; } .fa-th:before { content: "\f00a"; } .fa-th-list:before { content: "\f00b"; } .fa-check:before { content: "\f00c"; } .fa-times:before { content: "\f00d"; } .fa-search-plus:before { content: "\f00e"; } .fa-search-minus:before { content: "\f010"; } .fa-power-off:before { content: "\f011"; } .fa-signal:before { content: "\f012"; } .fa-gear:before, .fa-cog:before { content: "\f013"; } .fa-trash-o:before { content: "\f014"; } .fa-home:before { content: "\f015"; } .fa-file-o:before { content: "\f016"; } .fa-clock-o:before { content: "\f017"; } .fa-road:before { content: "\f018"; } .fa-download:before { content: "\f019"; } .fa-arrow-circle-o-down:before { content: "\f01a"; } .fa-arrow-circle-o-up:before { content: "\f01b"; } .fa-inbox:before { content: "\f01c"; } .fa-play-circle-o:before { content: "\f01d"; } .fa-rotate-right:before, .fa-repeat:before { content: "\f01e"; } .fa-refresh:before { content: "\f021"; } .fa-list-alt:before { content: "\f022"; } .fa-lock:before { content: "\f023"; } .fa-flag:before { content: "\f024"; } .fa-headphones:before { content: "\f025"; } .fa-volume-off:before { content: "\f026"; } .fa-volume-down:before { content: "\f027"; } .fa-volume-up:before { content: "\f028"; } .fa-qrcode:before { content: "\f029"; } .fa-barcode:before { content: "\f02a"; } .fa-tag:before { content: "\f02b"; } .fa-tags:before { content: "\f02c"; } .fa-book:before { content: "\f02d"; } .fa-bookmark:before { content: "\f02e"; } .fa-print:before { content: "\f02f"; } .fa-camera:before { content: "\f030"; } .fa-font:before { content: "\f031"; } .fa-bold:before { content: "\f032"; } .fa-italic:before { content: "\f033"; } .fa-text-height:before { content: "\f034"; } .fa-text-width:before { content: "\f035"; } .fa-align-left:before { content: "\f036"; } .fa-align-center:before { content: "\f037"; } .fa-align-right:before { content: "\f038"; } .fa-align-justify:before { content: "\f039"; } .fa-list:before { content: "\f03a"; } .fa-dedent:before, .fa-outdent:before { content: "\f03b"; } .fa-indent:before { content: "\f03c"; } .fa-video-camera:before { content: "\f03d"; } .fa-photo:before, .fa-image:before, .fa-picture-o:before { content: "\f03e"; } .fa-pencil:before { content: "\f040"; } .fa-map-marker:before { content: "\f041"; } .fa-adjust:before { content: "\f042"; } .fa-tint:before { content: "\f043"; } .fa-edit:before, .fa-pencil-square-o:before { content: "\f044"; } .fa-share-square-o:before { content: "\f045"; } .fa-check-square-o:before { content: "\f046"; } .fa-arrows:before { content: "\f047"; } .fa-step-backward:before { content: "\f048"; } .fa-fast-backward:before { content: "\f049"; } .fa-backward:before { content: "\f04a"; } .fa-play:before { content: "\f04b"; } .fa-pause:before { content: "\f04c"; } .fa-stop:before { content: "\f04d"; } .fa-forward:before { content: "\f04e"; } .fa-fast-forward:before { content: "\f050"; } .fa-step-forward:before { content: "\f051"; } .fa-eject:before { content: "\f052"; } .fa-chevron-left:before { content: "\f053"; } .fa-chevron-right:before { content: "\f054"; } .fa-plus-circle:before { content: "\f055"; } .fa-minus-circle:before { content: "\f056"; } .fa-times-circle:before { content: "\f057"; } .fa-check-circle:before { content: "\f058"; } .fa-question-circle:before { content: "\f059"; } .fa-info-circle:before { content: "\f05a"; } .fa-crosshairs:before { content: "\f05b"; } .fa-times-circle-o:before { content: "\f05c"; } .fa-check-circle-o:before { content: "\f05d"; } .fa-ban:before { content: "\f05e"; } .fa-arrow-left:before { content: "\f060"; } .fa-arrow-right:before { content: "\f061"; } .fa-arrow-up:before { content: "\f062"; } .fa-arrow-down:before { content: "\f063"; } .fa-mail-forward:before, .fa-share:before { content: "\f064"; } .fa-expand:before { content: "\f065"; } .fa-compress:before { content: "\f066"; } .fa-plus:before { content: "\f067"; } .fa-minus:before { content: "\f068"; } .fa-asterisk:before { content: "\f069"; } .fa-exclamation-circle:before { content: "\f06a"; } .fa-gift:before { content: "\f06b"; } .fa-leaf:before { content: "\f06c"; } .fa-fire:before { content: "\f06d"; } .fa-eye:before { content: "\f06e"; } .fa-eye-slash:before { content: "\f070"; } .fa-warning:before, .fa-exclamation-triangle:before { content: "\f071"; } .fa-plane:before { content: "\f072"; } .fa-calendar:before { content: "\f073"; } .fa-random:before { content: "\f074"; } .fa-comment:before { content: "\f075"; } .fa-magnet:before { content: "\f076"; } .fa-chevron-up:before { content: "\f077"; } .fa-chevron-down:before { content: "\f078"; } .fa-retweet:before { content: "\f079"; } .fa-shopping-cart:before { content: "\f07a"; } .fa-folder:before { content: "\f07b"; } .fa-folder-open:before { content: "\f07c"; } .fa-arrows-v:before { content: "\f07d"; } .fa-arrows-h:before { content: "\f07e"; } .fa-bar-chart-o:before { content: "\f080"; } .fa-twitter-square:before { content: "\f081"; } .fa-facebook-square:before { content: "\f082"; } .fa-camera-retro:before { content: "\f083"; } .fa-key:before { content: "\f084"; } .fa-gears:before, .fa-cogs:before { content: "\f085"; } .fa-comments:before { content: "\f086"; } .fa-thumbs-o-up:before { content: "\f087"; } .fa-thumbs-o-down:before { content: "\f088"; } .fa-star-half:before { content: "\f089"; } .fa-heart-o:before { content: "\f08a"; } .fa-sign-out:before { content: "\f08b"; } .fa-linkedin-square:before { content: "\f08c"; } .fa-thumb-tack:before { content: "\f08d"; } .fa-external-link:before { content: "\f08e"; } .fa-sign-in:before { content: "\f090"; } .fa-trophy:before { content: "\f091"; } .fa-github-square:before { content: "\f092"; } .fa-upload:before { content: "\f093"; } .fa-lemon-o:before { content: "\f094"; } .fa-phone:before { content: "\f095"; } .fa-square-o:before { content: "\f096"; } .fa-bookmark-o:before { content: "\f097"; } .fa-phone-square:before { content: "\f098"; } .fa-twitter:before { content: "\f099"; } .fa-facebook:before { content: "\f09a"; } .fa-github:before { content: "\f09b"; } .fa-unlock:before { content: "\f09c"; } .fa-credit-card:before { content: "\f09d"; } .fa-rss:before { content: "\f09e"; } .fa-hdd-o:before { content: "\f0a0"; } .fa-bullhorn:before { content: "\f0a1"; } .fa-bell:before { content: "\f0f3"; } .fa-certificate:before { content: "\f0a3"; } .fa-hand-o-right:before { content: "\f0a4"; } .fa-hand-o-left:before { content: "\f0a5"; } .fa-hand-o-up:before { content: "\f0a6"; } .fa-hand-o-down:before { content: "\f0a7"; } .fa-arrow-circle-left:before { content: "\f0a8"; } .fa-arrow-circle-right:before { content: "\f0a9"; } .fa-arrow-circle-up:before { content: "\f0aa"; } .fa-arrow-circle-down:before { content: "\f0ab"; } .fa-globe:before { content: "\f0ac"; } .fa-wrench:before { content: "\f0ad"; } .fa-tasks:before { content: "\f0ae"; } .fa-filter:before { content: "\f0b0"; } .fa-briefcase:before { content: "\f0b1"; } .fa-arrows-alt:before { content: "\f0b2"; } .fa-group:before, .fa-users:before { content: "\f0c0"; } .fa-chain:before, .fa-link:before { content: "\f0c1"; } .fa-cloud:before { content: "\f0c2"; } .fa-flask:before { content: "\f0c3"; } .fa-cut:before, .fa-scissors:before { content: "\f0c4"; } .fa-copy:before, .fa-files-o:before { content: "\f0c5"; } .fa-paperclip:before { content: "\f0c6"; } .fa-save:before, .fa-floppy-o:before { content: "\f0c7"; } .fa-square:before { content: "\f0c8"; } .fa-navicon:before, .fa-reorder:before, .fa-bars:before { content: "\f0c9"; } .fa-list-ul:before { content: "\f0ca"; } .fa-list-ol:before { content: "\f0cb"; } .fa-strikethrough:before { content: "\f0cc"; } .fa-underline:before { content: "\f0cd"; } .fa-table:before { content: "\f0ce"; } .fa-magic:before { content: "\f0d0"; } .fa-truck:before { content: "\f0d1"; } .fa-pinterest:before { content: "\f0d2"; } .fa-pinterest-square:before { content: "\f0d3"; } .fa-google-plus-square:before { content: "\f0d4"; } .fa-google-plus:before { content: "\f0d5"; } .fa-money:before { content: "\f0d6"; } .fa-caret-down:before { content: "\f0d7"; } .fa-caret-up:before { content: "\f0d8"; } .fa-caret-left:before { content: "\f0d9"; } .fa-caret-right:before { content: "\f0da"; } .fa-columns:before { content: "\f0db"; } .fa-unsorted:before, .fa-sort:before { content: "\f0dc"; } .fa-sort-down:before, .fa-sort-desc:before { content: "\f0dd"; } .fa-sort-up:before, .fa-sort-asc:before { content: "\f0de"; } .fa-envelope:before { content: "\f0e0"; } .fa-linkedin:before { content: "\f0e1"; } .fa-rotate-left:before, .fa-undo:before { content: "\f0e2"; } .fa-legal:before, .fa-gavel:before { content: "\f0e3"; } .fa-dashboard:before, .fa-tachometer:before { content: "\f0e4"; } .fa-comment-o:before { content: "\f0e5"; } .fa-comments-o:before { content: "\f0e6"; } .fa-flash:before, .fa-bolt:before { content: "\f0e7"; } .fa-sitemap:before { content: "\f0e8"; } .fa-umbrella:before { content: "\f0e9"; } .fa-paste:before, .fa-clipboard:before { content: "\f0ea"; } .fa-lightbulb-o:before { content: "\f0eb"; } .fa-exchange:before { content: "\f0ec"; } .fa-cloud-download:before { content: "\f0ed"; } .fa-cloud-upload:before { content: "\f0ee"; } .fa-user-md:before { content: "\f0f0"; } .fa-stethoscope:before { content: "\f0f1"; } .fa-suitcase:before { content: "\f0f2"; } .fa-bell-o:before { content: "\f0a2"; } .fa-coffee:before { content: "\f0f4"; } .fa-cutlery:before { content: "\f0f5"; } .fa-file-text-o:before { content: "\f0f6"; } .fa-building-o:before { content: "\f0f7"; } .fa-hospital-o:before { content: "\f0f8"; } .fa-ambulance:before { content: "\f0f9"; } .fa-medkit:before { content: "\f0fa"; } .fa-fighter-jet:before { content: "\f0fb"; } .fa-beer:before { content: "\f0fc"; } .fa-h-square:before { content: "\f0fd"; } .fa-plus-square:before { content: "\f0fe"; } .fa-angle-double-left:before { content: "\f100"; } .fa-angle-double-right:before { content: "\f101"; } .fa-angle-double-up:before { content: "\f102"; } .fa-angle-double-down:before { content: "\f103"; } .fa-angle-left:before { content: "\f104"; } .fa-angle-right:before { content: "\f105"; } .fa-angle-up:before { content: "\f106"; } .fa-angle-down:before { content: "\f107"; } .fa-desktop:before { content: "\f108"; } .fa-laptop:before { content: "\f109"; } .fa-tablet:before { content: "\f10a"; } .fa-mobile-phone:before, .fa-mobile:before { content: "\f10b"; } .fa-circle-o:before { content: "\f10c"; } .fa-quote-left:before { content: "\f10d"; } .fa-quote-right:before { content: "\f10e"; } .fa-spinner:before { content: "\f110"; } .fa-circle:before { content: "\f111"; } .fa-mail-reply:before, .fa-reply:before { content: "\f112"; } .fa-github-alt:before { content: "\f113"; } .fa-folder-o:before { content: "\f114"; } .fa-folder-open-o:before { content: "\f115"; } .fa-smile-o:before { content: "\f118"; } .fa-frown-o:before { content: "\f119"; } .fa-meh-o:before { content: "\f11a"; } .fa-gamepad:before { content: "\f11b"; } .fa-keyboard-o:before { content: "\f11c"; } .fa-flag-o:before { content: "\f11d"; } .fa-flag-checkered:before { content: "\f11e"; } .fa-terminal:before { content: "\f120"; } .fa-code:before { content: "\f121"; } .fa-mail-reply-all:before, .fa-reply-all:before { content: "\f122"; } .fa-star-half-empty:before, .fa-star-half-full:before, .fa-star-half-o:before { content: "\f123"; } .fa-location-arrow:before { content: "\f124"; } .fa-crop:before { content: "\f125"; } .fa-code-fork:before { content: "\f126"; } .fa-unlink:before, .fa-chain-broken:before { content: "\f127"; } .fa-question:before { content: "\f128"; } .fa-info:before { content: "\f129"; } .fa-exclamation:before { content: "\f12a"; } .fa-superscript:before { content: "\f12b"; } .fa-subscript:before { content: "\f12c"; } .fa-eraser:before { content: "\f12d"; } .fa-puzzle-piece:before { content: "\f12e"; } .fa-microphone:before { content: "\f130"; } .fa-microphone-slash:before { content: "\f131"; } .fa-shield:before { content: "\f132"; } .fa-calendar-o:before { content: "\f133"; } .fa-fire-extinguisher:before { content: "\f134"; } .fa-rocket:before { content: "\f135"; } .fa-maxcdn:before { content: "\f136"; } .fa-chevron-circle-left:before { content: "\f137"; } .fa-chevron-circle-right:before { content: "\f138"; } .fa-chevron-circle-up:before { content: "\f139"; } .fa-chevron-circle-down:before { content: "\f13a"; } .fa-html5:before { content: "\f13b"; } .fa-css3:before { content: "\f13c"; } .fa-anchor:before { content: "\f13d"; } .fa-unlock-alt:before { content: "\f13e"; } .fa-bullseye:before { content: "\f140"; } .fa-ellipsis-h:before { content: "\f141"; } .fa-ellipsis-v:before { content: "\f142"; } .fa-rss-square:before { content: "\f143"; } .fa-play-circle:before { content: "\f144"; } .fa-ticket:before { content: "\f145"; } .fa-minus-square:before { content: "\f146"; } .fa-minus-square-o:before { content: "\f147"; } .fa-level-up:before { content: "\f148"; } .fa-level-down:before { content: "\f149"; } .fa-check-square:before { content: "\f14a"; } .fa-pencil-square:before { content: "\f14b"; } .fa-external-link-square:before { content: "\f14c"; } .fa-share-square:before { content: "\f14d"; } .fa-compass:before { content: "\f14e"; } .fa-toggle-down:before, .fa-caret-square-o-down:before { content: "\f150"; } .fa-toggle-up:before, .fa-caret-square-o-up:before { content: "\f151"; } .fa-toggle-right:before, .fa-caret-square-o-right:before { content: "\f152"; } .fa-euro:before, .fa-eur:before { content: "\f153"; } .fa-gbp:before { content: "\f154"; } .fa-dollar:before, .fa-usd:before { content: "\f155"; } .fa-rupee:before, .fa-inr:before { content: "\f156"; } .fa-cny:before, .fa-rmb:before, .fa-yen:before, .fa-jpy:before { content: "\f157"; } .fa-ruble:before, .fa-rouble:before, .fa-rub:before { content: "\f158"; } .fa-won:before, .fa-krw:before { content: "\f159"; } .fa-bitcoin:before, .fa-btc:before { content: "\f15a"; } .fa-file:before { content: "\f15b"; } .fa-file-text:before { content: "\f15c"; } .fa-sort-alpha-asc:before { content: "\f15d"; } .fa-sort-alpha-desc:before { content: "\f15e"; } .fa-sort-amount-asc:before { content: "\f160"; } .fa-sort-amount-desc:before { content: "\f161"; } .fa-sort-numeric-asc:before { content: "\f162"; } .fa-sort-numeric-desc:before { content: "\f163"; } .fa-thumbs-up:before { content: "\f164"; } .fa-thumbs-down:before { content: "\f165"; } .fa-youtube-square:before { content: "\f166"; } .fa-youtube:before { content: "\f167"; } .fa-xing:before { content: "\f168"; } .fa-xing-square:before { content: "\f169"; } .fa-youtube-play:before { content: "\f16a"; } .fa-dropbox:before { content: "\f16b"; } .fa-stack-overflow:before { content: "\f16c"; } .fa-instagram:before { content: "\f16d"; } .fa-flickr:before { content: "\f16e"; } .fa-adn:before { content: "\f170"; } .fa-bitbucket:before { content: "\f171"; } .fa-bitbucket-square:before { content: "\f172"; } .fa-tumblr:before { content: "\f173"; } .fa-tumblr-square:before { content: "\f174"; } .fa-long-arrow-down:before { content: "\f175"; } .fa-long-arrow-up:before { content: "\f176"; } .fa-long-arrow-left:before { content: "\f177"; } .fa-long-arrow-right:before { content: "\f178"; } .fa-apple:before { content: "\f179"; } .fa-windows:before { content: "\f17a"; } .fa-android:before { content: "\f17b"; } .fa-linux:before { content: "\f17c"; } .fa-dribbble:before { content: "\f17d"; } .fa-skype:before { content: "\f17e"; } .fa-foursquare:before { content: "\f180"; } .fa-trello:before { content: "\f181"; } .fa-female:before { content: "\f182"; } .fa-male:before { content: "\f183"; } .fa-gittip:before { content: "\f184"; } .fa-sun-o:before { content: "\f185"; } .fa-moon-o:before { content: "\f186"; } .fa-archive:before { content: "\f187"; } .fa-bug:before { content: "\f188"; } .fa-vk:before { content: "\f189"; } .fa-weibo:before { content: "\f18a"; } .fa-renren:before { content: "\f18b"; } .fa-pagelines:before { content: "\f18c"; } .fa-stack-exchange:before { content: "\f18d"; } .fa-arrow-circle-o-right:before { content: "\f18e"; } .fa-arrow-circle-o-left:before { content: "\f190"; } .fa-toggle-left:before, .fa-caret-square-o-left:before { content: "\f191"; } .fa-dot-circle-o:before { content: "\f192"; } .fa-wheelchair:before { content: "\f193"; } .fa-vimeo-square:before { content: "\f194"; } .fa-turkish-lira:before, .fa-try:before { content: "\f195"; } .fa-plus-square-o:before { content: "\f196"; } .fa-space-shuttle:before { content: "\f197"; } .fa-slack:before { content: "\f198"; } .fa-envelope-square:before { content: "\f199"; } .fa-wordpress:before { content: "\f19a"; } .fa-openid:before { content: "\f19b"; } .fa-institution:before, .fa-bank:before, .fa-university:before { content: "\f19c"; } .fa-mortar-board:before, .fa-graduation-cap:before { content: "\f19d"; } .fa-yahoo:before { content: "\f19e"; } .fa-google:before { content: "\f1a0"; } .fa-reddit:before { content: "\f1a1"; } .fa-reddit-square:before { content: "\f1a2"; } .fa-stumbleupon-circle:before { content: "\f1a3"; } .fa-stumbleupon:before { content: "\f1a4"; } .fa-delicious:before { content: "\f1a5"; } .fa-digg:before { content: "\f1a6"; } .fa-pied-piper-square:before, .fa-pied-piper:before { content: "\f1a7"; } .fa-pied-piper-alt:before { content: "\f1a8"; } .fa-drupal:before { content: "\f1a9"; } .fa-joomla:before { content: "\f1aa"; } .fa-language:before { content: "\f1ab"; } .fa-fax:before { content: "\f1ac"; } .fa-building:before { content: "\f1ad"; } .fa-child:before { content: "\f1ae"; } .fa-paw:before { content: "\f1b0"; } .fa-spoon:before { content: "\f1b1"; } .fa-cube:before { content: "\f1b2"; } .fa-cubes:before { content: "\f1b3"; } .fa-behance:before { content: "\f1b4"; } .fa-behance-square:before { content: "\f1b5"; } .fa-steam:before { content: "\f1b6"; } .fa-steam-square:before { content: "\f1b7"; } .fa-recycle:before { content: "\f1b8"; } .fa-automobile:before, .fa-car:before { content: "\f1b9"; } .fa-cab:before, .fa-taxi:before { content: "\f1ba"; } .fa-tree:before { content: "\f1bb"; } .fa-spotify:before { content: "\f1bc"; } .fa-deviantart:before { content: "\f1bd"; } .fa-soundcloud:before { content: "\f1be"; } .fa-database:before { content: "\f1c0"; } .fa-file-pdf-o:before { content: "\f1c1"; } .fa-file-word-o:before { content: "\f1c2"; } .fa-file-excel-o:before { content: "\f1c3"; } .fa-file-powerpoint-o:before { content: "\f1c4"; } .fa-file-photo-o:before, .fa-file-picture-o:before, .fa-file-image-o:before { content: "\f1c5"; } .fa-file-zip-o:before, .fa-file-archive-o:before { content: "\f1c6"; } .fa-file-sound-o:before, .fa-file-audio-o:before { content: "\f1c7"; } .fa-file-movie-o:before, .fa-file-video-o:before { content: "\f1c8"; } .fa-file-code-o:before { content: "\f1c9"; } .fa-vine:before { content: "\f1ca"; } .fa-codepen:before { content: "\f1cb"; } .fa-jsfiddle:before { content: "\f1cc"; } .fa-life-bouy:before, .fa-life-saver:before, .fa-support:before, .fa-life-ring:before { content: "\f1cd"; } .fa-circle-o-notch:before { content: "\f1ce"; } .fa-ra:before, .fa-rebel:before { content: "\f1d0"; } .fa-ge:before, .fa-empire:before { content: "\f1d1"; } .fa-git-square:before { content: "\f1d2"; } .fa-git:before { content: "\f1d3"; } .fa-hacker-news:before { content: "\f1d4"; } .fa-tencent-weibo:before { content: "\f1d5"; } .fa-qq:before { content: "\f1d6"; } .fa-wechat:before, .fa-weixin:before { content: "\f1d7"; } .fa-send:before, .fa-paper-plane:before { content: "\f1d8"; } .fa-send-o:before, .fa-paper-plane-o:before { content: "\f1d9"; } .fa-history:before { content: "\f1da"; } .fa-circle-thin:before { content: "\f1db"; } .fa-header:before { content: "\f1dc"; } .fa-paragraph:before { content: "\f1dd"; } .fa-sliders:before { content: "\f1de"; } .fa-share-alt:before { content: "\f1e0"; } .fa-share-alt-square:before { content: "\f1e1"; } .fa-bomb:before { content: "\f1e2"; } /* Magnific Popup CSS */ .mfp-bg { position: fixed; top: 0; left: 0; z-index: 502; width: 100%; height: 100%; overflow: hidden; background: #0b0b0b; opacity: 0.8; filter: alpha(opacity=80); } .mfp-wrap { position: fixed; top: 0; left: 0; z-index: 503; width: 100%; height: 100%; outline: none !important; -webkit-backface-visibility: hidden; } .mfp-container { position: absolute; top: 0; left: 0; width: 100%; height: 100%; padding: 0 8px; text-align: center; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .mfp-container:before { display: inline-block; height: 100%; vertical-align: middle; content: ''; } .mfp-align-top .mfp-container:before { display: none; } .mfp-content { position: relative; z-index: 505; display: inline-block; margin: 0 auto; text-align: left; vertical-align: middle; } .mfp-inline-holder .mfp-content, .mfp-ajax-holder .mfp-content { width: 100%; cursor: auto; } .mfp-inline-holder .mfp-content:after, .mfp-ajax-holder .mfp-content:after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: -1; display: block; width: auto; height: auto; content: ''; box-shadow: 0 0 8px rgba(0, 0, 0, 0.6); } .mfp-ajax-cur { cursor: progress; } .mfp-zoom-out-cur, .mfp-zoom-out-cur .mfp-image-holder .mfp-close { cursor: -moz-zoom-out; cursor: -webkit-zoom-out; cursor: zoom-out; } .mfp-zoom, .image-popup { cursor: pointer; cursor: -webkit-zoom-in; cursor: -moz-zoom-in; cursor: zoom-in; } .mfp-auto-cursor .mfp-content { cursor: auto; } .mfp-close, .mfp-arrow, .mfp-preloader, .mfp-counter { -webkit-user-select: none; -moz-user-select: none; user-select: none; } .mfp-loading.mfp-figure { display: none; } .mfp-hide { display: none !important; } .mfp-preloader { position: absolute; top: 50%; right: 8px; left: 8px; z-index: 504; width: auto; margin-top: -0.8em; color: #cccccc; text-align: center; } .mfp-preloader a { color: #cccccc; } .mfp-preloader a:hover { color: white; } .mfp-s-ready .mfp-preloader { display: none; } .mfp-s-error .mfp-content { display: none; } button.mfp-close, button.mfp-arrow { z-index: 506; display: block; padding: 0; overflow: visible; cursor: pointer; border: 0; -webkit-appearance: none; } button.mfp-close { background: transparent; } button::-moz-focus-inner { padding: 0; border: 0; } .mfp-close { position: absolute; top: 0; right: 0; width: 44px; height: 44px; padding: 0 0 18px 10px; font-family: Arial, Baskerville, monospace; font-size: 28px; font-style: normal; line-height: 44px; color: white; text-align: center; text-decoration: none; opacity: 0.65; } .mfp-close:hover, .mfp-close:focus { opacity: 1; } .mfp-close:active { top: 1px; } .mfp-close-btn-in .mfp-close { color: #333333; } .mfp-image-holder .mfp-close, .mfp-iframe-holder .mfp-close { right: -6px; width: 100%; padding-right: 6px; color: white; text-align: right; } .mfp-counter { position: absolute; top: 0; right: 0; font-size: 12px; line-height: 18px; color: #cccccc; } .mfp-arrow { position: absolute; top: 0; top: 50%; width: 90px; height: 110px; padding: 0; margin: 0; margin-top: -55px; overflow: hidden; text-indent: 100%; white-space: nowrap; background-color: #000000; opacity: 0.65; -webkit-transition: opacity 0.2s ease-in-out; -moz-transition: opacity 0.2s ease-in-out; -ms-transition: opacity 0.2s ease-in-out; -o-transition: opacity 0.2s ease-in-out; transition: opacity 0.2s ease-in-out; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } .mfp-arrow:active { margin-top: -54px; } .mfp-arrow:hover, .mfp-arrow:focus { opacity: 1; } .mfp-arrow:before, .mfp-arrow:after, .mfp-arrow .mfp-b, .mfp-arrow .mfp-a { position: absolute; top: 0; left: 0; display: block; width: 0; height: 0; margin-top: 35px; margin-left: 35px; border: solid transparent; content: ''; } .mfp-arrow:after, .mfp-arrow .mfp-a { top: 8px; border-top-width: 12px; border-bottom-width: 12px; opacity: 0.8; } .mfp-arrow:before, .mfp-arrow .mfp-b { border-top-width: 20px; border-bottom-width: 20px; } .mfp-arrow-left { left: 0; -webkit-border-top-right-radius: 5px; border-top-right-radius: 5px; -webkit-border-bottom-right-radius: 5px; border-bottom-right-radius: 5px; -webkit-border-bottom-left-radius: 0; border-bottom-left-radius: 0; -webkit-border-top-left-radius: 0; border-top-left-radius: 0; -moz-border-radius-topright: 5px; -moz-border-radius-bottomright: 5px; -moz-border-radius-bottomleft: 0; -moz-border-radius-topleft: 0; -webkit-background-clip: padding-box; -moz-background-clip: padding; background-clip: padding-box; } .mfp-arrow-left:after, .mfp-arrow-left .mfp-a { left: 5px; border-right: 12px solid #fff; } .mfp-arrow-left:before, .mfp-arrow-left .mfp-b { border-right: 20px solid #fff; } .mfp-arrow-right { right: 0; -webkit-border-top-right-radius: 0; border-top-right-radius: 0; -webkit-border-bottom-right-radius: 0; border-bottom-right-radius: 0; -webkit-border-bottom-left-radius: 5px; border-bottom-left-radius: 5px; -webkit-border-top-left-radius: 5px; border-top-left-radius: 5px; -moz-border-radius-topright: 0; -moz-border-radius-bottomright: 0; -moz-border-radius-bottomleft: 5px; -moz-border-radius-topleft: 5px; -webkit-background-clip: padding-box; -moz-background-clip: padding; background-clip: padding-box; } .mfp-arrow-right:after, .mfp-arrow-right .mfp-a { left: 3px; border-left: 12px solid #fff; } .mfp-arrow-right:before, .mfp-arrow-right .mfp-b { border-left: 20px solid #fff; } .mfp-iframe-holder { padding-top: 40px; padding-bottom: 40px; } .mfp-iframe-holder .mfp-content { width: 100%; max-width: 900px; line-height: 0; } .mfp-iframe-scaler { width: 100%; height: 0; padding-top: 56.25%; overflow: hidden; } .mfp-iframe-scaler iframe { position: absolute; top: 0; left: 0; display: block; width: 100%; height: 100%; background: black; box-shadow: 0 0 8px rgba(0, 0, 0, 0.6); } .mfp-iframe-holder .mfp-close { top: -40px; } /* Main image in popup */ img.mfp-img { display: block; width: auto; height: auto; max-width: 100%; padding: 40px 0 40px; margin: 0 auto; line-height: 0; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } /* The shadow behind the image */ .mfp-figure:after { position: absolute; top: 40px; right: 0; bottom: 40px; left: 0; z-index: -1; display: block; width: auto; height: auto; content: ''; box-shadow: 0 0 8px rgba(0, 0, 0, 0.6); } .mfp-figure { line-height: 0; } .mfp-bottom-bar { position: absolute; top: 100%; left: 0; width: 100%; margin-top: -36px; cursor: auto; } .mfp-title { line-height: 18px; color: #f3f3f3; text-align: left; } .mfp-figure small { display: block; font-size: 12px; line-height: 14px; color: #bdbdbd; } .mfp-image-holder .mfp-content { max-width: 100%; } .mfp-gallery .mfp-image-holder .mfp-figure { cursor: pointer; } @media screen and (max-width: 800px) and (orientation: landscape), screen and (max-height: 300px) { { /** * Remove all paddings around the image on small screen */ /* The shadow behind the image */ } .mfp-img-mobile .mfp-image-holder { padding-right: 0; padding-left: 0; } .mfp-img-mobile img.mfp-img { padding: 0; } .mfp-img-mobile .mfp-figure:after { top: 0; bottom: 0; } .mfp-img-mobile .mfp-bottom-bar { position: fixed; top: auto; bottom: 0; padding: 3px 5px; margin: 0; background: rgba(0, 0, 0, 0.6); -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .mfp-img-mobile .mfp-bottom-bar:empty { padding: 0; } .mfp-img-mobile .mfp-counter { top: 3px; right: 5px; } .mfp-img-mobile .mfp-close { position: fixed; top: 0; right: 0; width: 35px; height: 35px; padding: 0; line-height: 35px; text-align: center; background: rgba(0, 0, 0, 0.6); } .mfp-img-mobile .mfp-figure small { display: inline; margin-left: 5px; } } @media all and (max-width: 800px) { .mfp-arrow { -webkit-transform: scale(0.75); transform: scale(0.75); } .mfp-arrow-left { -webkit-transform-origin: 0; transform-origin: 0; } .mfp-arrow-right { -webkit-transform-origin: 100%; transform-origin: 100%; } .mfp-container { padding-right: 6px; padding-left: 6px; } } .mfp-ie7 .mfp-img { padding: 0; } .mfp-ie7 .mfp-bottom-bar { left: 50%; width: 600px; padding-bottom: 5px; margin-top: 5px; margin-left: -300px; } .mfp-ie7 .mfp-container { padding: 0; } .mfp-ie7 .mfp-content { padding-top: 44px; } .mfp-ie7 .mfp-close { top: 0; right: 0; padding-top: 0; } /** * Fade-zoom animation for first dialog */ /* start state */ .mfp-fade .zoom-anim-dialog { opacity: 0; -webkit-transform: scale(0.8); -moz-transform: scale(0.8); -ms-transform: scale(0.8); -o-transform: scale(0.8); transform: scale(0.8); -webkit-transition: all 0.2s ease-in-out; -moz-transition: all 0.2s ease-in-out; -ms-transition: all 0.2s ease-in-out; -o-transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out; } /* animate in */ .mfp-fade.mfp-ready .zoom-anim-dialog { opacity: 1; -webkit-transform: scale(1); -moz-transform: scale(1); -ms-transform: scale(1); -o-transform: scale(1); transform: scale(1); } /* animate out */ .mfp-fade.mfp-removing .zoom-anim-dialog { opacity: 0; -webkit-transform: scale(0.8); -moz-transform: scale(0.8); -ms-transform: scale(0.8); -o-transform: scale(0.8); transform: scale(0.8); } /* Dark overlay, start state */ .mfp-fade.mfp-bg { opacity: 0; -webkit-transition: opacity 0.3s ease-out; -moz-transition: opacity 0.3s ease-out; -ms-transition: opacity 0.3s ease-out; -o-transition: opacity 0.3s ease-out; transition: opacity 0.3s ease-out; } /* animate in */ .mfp-fade.mfp-ready.mfp-bg { opacity: 0.8; } /* animate out */ .mfp-fade.mfp-removing.mfp-bg { opacity: 0; } body { width: 100%; padding: 0; margin: 0; background-color: #e8e8e8; } .entry, .hentry { *zoom: 1; } .entry:before, .hentry:before, .entry:after, .hentry:after { display: table; line-height: 0; content: ""; } .entry:after, .hentry:after { clear: both; } .entry-content { margin-bottom: 26px; margin-bottom: 1.625rem; font-size: 16px; font-size: 1rem; line-height: 1.625; } .entry-content p > a, .entry-content li > a { border-bottom: 1px dotted #a2a2a2; } .entry-content p > a:hover, .entry-content li > a:hover { border-bottom-style: solid; } .entry-header { position: relative; width: 100%; overflow: hidden; } .header-title { margin: 30px 0 0; text-align: center; } .header-title h1 { margin: 10px 20px; font-size: 28px; font-size: 1.75rem; font-weight: 700; color: #555555; } @media only screen and (min-width: 48em) { .header-title h1 { font-size: 48px; font-size: 3rem; } } @media only screen and (min-width: 62.5em) { .header-title h1 { font-size: 60px; font-size: 3.75rem; } } .header-title h2 { margin: 0; font-size: 18px; font-size: 1.125rem; color: #888888; text-transform: uppercase; } @media only screen and (min-width: 48em) { .header-title h2 { font-size: 30px; font-size: 1.875rem; } } .header-title p { color: #555555; } .feature .header-title { position: absolute; top: 0; display: table; width: 100%; height: 400px; margin-top: 0; overflow: hidden; } .feature .header-title .header-title-wrap { display: table-cell; margin: 0 auto; text-align: center; vertical-align: middle; } .feature .header-title h1 { margin: 10px; margin: 10px 60px; font-weight: 700; color: #ffffff; text-shadow: 1px 1px 4px rgba(34, 34, 34, 0.6); } .feature .header-title h1 a { color: #ffffff; } @media only screen and (min-width: 62.5em) { } .feature .header-title h2 { margin: 0; color: #ffffff; text-transform: uppercase; } @media only screen and (min-width: 48em) { .feature .header-title h2 a { color: #ffffff; } } .feature .header-title p { color: #ffffff; } .entry-image { position: relative; top: -50%; left: -50%; width: 200%; height: 200%; min-height: 400px; overflow: hidden; } .entry-image:after { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(34, 34, 34, 0.3); content: ''; } .entry-image img { position: absolute; top: 0; right: 0; bottom: 0; left: 0; min-width: 50%; min-height: 50%; margin: auto; } .image-credit { position: absolute; right: 0; bottom: 0; z-index: 10; max-width: 440px; padding: 10px 15px; margin: 0 auto; font-size: 12px; font-size: 0.75rem; color: #ffffff; text-align: right; background-color: rgba(34, 34, 34, 0.5); -webkit-border-top-right-radius: 3px; border-top-right-radius: 3px; -webkit-border-bottom-right-radius: 0; border-bottom-right-radius: 0; -webkit-border-bottom-left-radius: 0; border-bottom-left-radius: 0; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-topright: 3px; -moz-border-radius-bottomright: 0; -moz-border-radius-bottomleft: 0; -moz-border-radius-topleft: 3px; -webkit-background-clip: padding-box; -moz-background-clip: padding; background-clip: padding-box; } @media only screen and (min-width: 48em) { .image-credit { max-width: 760px; } } @media only screen and (min-width: 62.5em) { .image-credit { max-width: 960px; } } .image-credit a { color: #ffffff; text-decoration: none; } .entry-meta { font-size: 12px; font-size: 0.75rem; color: #bbbbbb; text-transform: uppercase; } .entry-meta a { color: #bbbbbb; } .entry-meta .vcard:before { content: " by "; } .entry-meta .tag { display: inline-block; padding: 2px 6px; margin: 4px; color: #ffffff; background-color: #bbbbbb; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } .entry-meta .tag span { font-size: 10px; font-size: 0.625rem; vertical-align: super; } .entry-meta .tag:hover { background-color: #a2a2a2; } .entry-meta .tag:active { -webkit-transform: translate(0, 1px); -moz-transform: translate(0, 1px); -ms-transform: translate(0, 1px); -o-transform: translate(0, 1px); transform: translate(0, 1px); -webkit-box-shadow: 0 0 1px rgba(34, 34, 34, 0.2); -moz-box-shadow: 0 0 1px rgba(34, 34, 34, 0.2); box-shadow: 0 0 1px rgba(34, 34, 34, 0.2); } #post .entry-content, #page .entry-content { padding: 10px 15px; margin: 40px 2px 20px 2px; background-color: #ffffff; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; box-shadow: 0 0 0 0, 0 6px 12px rgba(0, 0, 0, 0.1); } @media only screen and (min-width: 48em) { #post .entry-content, #page .entry-content { padding: 20px 30px; margin-right: 10px; margin-left: 10px; } } @media only screen and (min-width: 62.5em) { #post .entry-content, #page .entry-content { max-width: 800px; padding: 50px 80px; margin: 50px auto 30px auto; } #post .entry-content > p:first-child, #page .entry-content > p:first-child { margin-bottom: 26px; margin-bottom: 1.625rem; font-size: 20px; font-size: 1.25rem; line-height: 1.3; } } #post #disqus_thread, #page #disqus_thread { padding: 10px 15px; margin: 40px 2px 20px 2px; background-color: #ffffff; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; box-shadow: 0 0 0 1px rgba(187, 187, 187, 0.1), 0 6px 12px rgba(0, 0, 0, 0.1); } @media only screen and (min-width: 48em) { #post #disqus_thread, #page #disqus_thread { padding: 20px 30px; margin-right: 10px; margin-left: 10px; } } @media only screen and (min-width: 62.5em) { #post #disqus_thread, #page #disqus_thread { max-width: 800px; padding: 50px 80px; margin: 0 auto 30px auto; } } #post .entry-meta, #page .entry-meta { margin: 50px 30px 30px; text-align: center; } .entry-tags { display: block; margin-bottom: 6px; } .pagination { margin: 20px 10px; text-align: center; } .pagination ul { display: inline; margin-right: 10px; margin-left: 10px; } .pagination li { padding-right: 4px; padding-left: 4px; } .pagination .current-page { font-weight: 700; } .read-more { position: relative; padding: 40px 15px 25px; margin: 40px 2px 20px 2px; text-align: center; background-color: #ffffff; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; *zoom: 1; box-shadow: 0 0 0 1px rgba(187, 187, 187, 0.1), 0 6px 12px rgba(0, 0, 0, 0.1); } @media only screen and (min-width: 48em) { .read-more { padding: 50px 40px 25px; margin: 50px 10px 20px 10px; } } @media only screen and (min-width: 62.5em) { .read-more { max-width: 800px; padding: 50px 80px; margin: 60px auto; } } .read-more:before, .read-more:after { display: table; line-height: 0; content: ""; } .read-more:after { clear: both; } .read-more-header { position: absolute; top: -20px; right: 0; left: 0; height: 35px; } .read-more-header a { display: inline-block; padding: 8px 20px; margin-bottom: 20px; font-size: 14px; font-size: 0.875rem; color: #ffffff; background-color: #222222; border-color: #222222; border-style: solid !important; border-width: 2px !important; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } .read-more-header a:visited { color: #ffffff; } .read-more-header a:hover { color: #222222; background-color: #ffffff; } .read-more-content { margin-bottom: 26px; margin-bottom: 1.625rem; font-size: 16px; font-size: 1rem; line-height: 1.625; } .read-more-content p > a, .read-more-content li > a { border-bottom: 1px dotted #a2a2a2; } .read-more-content p > a:hover, .read-more-content li > a:hover { border-bottom-style: solid; } .read-more-content h3 { margin: 0; font-size: 28px; font-size: 1.75rem; } .read-more-content h3 a { color: #222222; } @media only screen and (min-width: 48em) { .read-more-content h3 { font-size: 36px; font-size: 2.25rem; } } .read-more-list { border-top: solid 1px #bbbbbb; } .list-item { width: 100%; text-align: left; } .list-item h4 { margin-bottom: 0; font-size: 18px; font-size: 1.125rem; } .list-item span { display: block; font-size: 14px; font-size: 0.875rem; color: #a2a2a2; } @media only screen and (min-width: 48em) { .list-item { float: left; width: 49%; } .list-item:nth-child(2) { text-align: right; } } #post-index #main { margin: 40px 2px 20px 2px; } @media only screen and (min-width: 48em) { #post-index #main { margin-right: 20px; margin-left: 20px; } } @media only screen and (min-width: 62.5em) { #post-index #main { max-width: 800px; margin-top: 50px; margin-right: auto; margin-left: auto; } } #post-index article { padding: 25px 15px; margin-bottom: 20px; background-color: #ffffff; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; box-shadow: 0 0 0 0, 0 6px 12px rgba(34, 34, 34, 0.1); } @media only screen and (min-width: 48em) { #post-index article { padding: 30px; } } @media only screen and (min-width: 62.5em) { #post-index article { padding: 50px 80px; margin-bottom: 30px; } } .footer-wrapper { margin: 2em auto; color: #555555; text-align: center; *zoom: 1; } .footer-wrapper:before, .footer-wrapper:after { display: table; line-height: 0; content: ""; } .footer-wrapper:after { clear: both; } .footer-wrapper a { color: #555555; } .socialcount { font-size: 16px; font-size: 1rem; font-weight: 700; } .socialcount li { padding-right: 10px; padding-left: 10px; } .socialcount p > a, .socialcount li > a { border-bottom-width: 0; } .upgrade { padding: 10px; text-align: center; } #goog-fixurl ul { padding-left: 0; margin-left: 0; list-style: none; } #goog-fixurl ul li { list-style-type: none; } #goog-wm-qt { display: inline-block; width: auto; padding: 8px 20px; margin-right: 10px; margin-bottom: 20px; font-size: 14px; font-size: 0.875rem; color: #222222; background-color: #ffffff; border-color: #a2a2a2; border-style: solid !important; border-width: 2px !important; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } #goog-wm-sb { display: inline-block; padding: 8px 20px; margin-bottom: 20px; font-size: 14px; font-size: 0.875rem; color: #ffffff; background-color: #222222; border-color: #222222; border-style: solid !important; border-width: 2px !important; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } #goog-wm-sb:visited { color: #ffffff; } #goog-wm-sb:hover { color: #222222; background-color: #ffffff; } ================================================ FILE: assets/js/_main.js ================================================ /*! Plugin options and other jQuery stuff */ // dl-menu options $(function() { $( '#dl-menu' ).dlmenu({ animationClasses : { classin : 'dl-animate-in', classout : 'dl-animate-out' } }); }); // FitVids options $(function() { $("article").fitVids(); }); $(".close-menu").click(function () { $(".menu").toggleClass("disabled"); $(".links").toggleClass("enabled"); }); $(".about").click(function () { $("#about").css('display','block'); }); $(".close-about").click(function () { $("#about").css('display',''); }); // Add lightbox class to all image links $("a[href$='.jpg'],a[href$='.jpeg'],a[href$='.JPG'],a[href$='.png'],a[href$='.gif']").addClass("image-popup"); // Magnific-Popup options $(document).ready(function() { $('.image-popup').magnificPopup({ type: 'image', tLoading: 'Loading image #%curr%...', gallery: { enabled: true, navigateByImgClick: true, preload: [0,1] // Will preload 0 - before current, and 1 after the current image }, image: { tError: 'Image #%curr% could not be loaded.', }, removalDelay: 300, // Delay in milliseconds before popup is removed // Class that is added to body when popup is open. // make it unique to apply your CSS animations just to this exact popup mainClass: 'mfp-fade' }); }); ================================================ FILE: assets/js/plugins/jquery.dlmenu.js ================================================ /** * jquery.dlmenu.js v1.0.1 * http://www.codrops.com * * Licensed under the MIT license. * http://www.opensource.org/licenses/mit-license.php * * Copyright 2013, Codrops * http://www.codrops.com */ ;( function( $, window, undefined ) { 'use strict'; // global var Modernizr = window.Modernizr, $body = $( 'body' ); $.DLMenu = function( options, element ) { this.$el = $( element ); this._init( options ); }; // the options $.DLMenu.defaults = { // classes for the animation effects animationClasses : { classin : 'dl-animate-in-1', classout : 'dl-animate-out-1' }, // callback: click a link that has a sub menu // el is the link element (li); name is the level name onLevelClick : function( el, name ) { return false; }, // callback: click a link that does not have a sub menu // el is the link element (li); ev is the event obj onLinkClick : function( el, ev ) { return false; } }; $.DLMenu.prototype = { _init : function( options ) { // options this.options = $.extend( true, {}, $.DLMenu.defaults, options ); // cache some elements and initialize some variables this._config(); var animEndEventNames = { 'WebkitAnimation' : 'webkitAnimationEnd', 'OAnimation' : 'oAnimationEnd', 'msAnimation' : 'MSAnimationEnd', 'animation' : 'animationend' }, transEndEventNames = { 'WebkitTransition' : 'webkitTransitionEnd', 'MozTransition' : 'transitionend', 'OTransition' : 'oTransitionEnd', 'msTransition' : 'MSTransitionEnd', 'transition' : 'transitionend' }; // animation end event name this.animEndEventName = animEndEventNames[ Modernizr.prefixed( 'animation' ) ] + '.dlmenu'; // transition end event name this.transEndEventName = transEndEventNames[ Modernizr.prefixed( 'transition' ) ] + '.dlmenu', // support for css animations and css transitions this.supportAnimations = Modernizr.cssanimations, this.supportTransitions = Modernizr.csstransitions; this._initEvents(); }, _config : function() { this.open = false; this.$trigger = this.$el.children( '.dl-trigger' ); this.$menu = this.$el.children( 'ul.dl-menu' ); this.$menuitems = this.$menu.find( 'li:not(.dl-back)' ); this.$el.find( 'ul.dl-submenu' ).prepend( '
  • back
  • ' ); this.$back = this.$menu.find( 'li.dl-back' ); }, _initEvents : function() { var self = this; this.$trigger.on( 'click.dlmenu', function() { if( self.open ) { self._closeMenu(); } else { self._openMenu(); } return false; } ); this.$menuitems.on( 'click.dlmenu', function( event ) { event.stopPropagation(); var $item = $(this), $submenu = $item.children( 'ul.dl-submenu' ); if( $submenu.length > 0 ) { var $flyin = $submenu.clone().css({ opacity: 0, margin: 0 }).insertAfter( self.$menu ), onAnimationEndFn = function() { self.$menu.off( self.animEndEventName ).removeClass( self.options.animationClasses.classout ).addClass( 'dl-subview' ); $item.addClass( 'dl-subviewopen' ).parents( '.dl-subviewopen:first' ).removeClass( 'dl-subviewopen' ).addClass( 'dl-subview' ); $flyin.remove(); }; setTimeout( function() { $flyin.addClass( self.options.animationClasses.classin ); self.$menu.addClass( self.options.animationClasses.classout ); if( self.supportAnimations ) { self.$menu.on( self.animEndEventName, onAnimationEndFn ); } else { onAnimationEndFn.call(); } self.options.onLevelClick( $item, $item.children( 'a:first' ).text() ); } ); return false; } else { self.options.onLinkClick( $item, event ); } } ); this.$back.on( 'click.dlmenu', function( event ) { var $this = $( this ), $submenu = $this.parents( 'ul.dl-submenu:first' ), $item = $submenu.parent(), $flyin = $submenu.clone().insertAfter( self.$menu ); var onAnimationEndFn = function() { self.$menu.off( self.animEndEventName ).removeClass( self.options.animationClasses.classin ); $flyin.remove(); }; setTimeout( function() { $flyin.addClass( self.options.animationClasses.classout ); self.$menu.addClass( self.options.animationClasses.classin ); if( self.supportAnimations ) { self.$menu.on( self.animEndEventName, onAnimationEndFn ); } else { onAnimationEndFn.call(); } $item.removeClass( 'dl-subviewopen' ); var $subview = $this.parents( '.dl-subview:first' ); if( $subview.is( 'li' ) ) { $subview.addClass( 'dl-subviewopen' ); } $subview.removeClass( 'dl-subview' ); } ); return false; } ); }, closeMenu : function() { if( this.open ) { this._closeMenu(); } }, _closeMenu : function() { var self = this, onTransitionEndFn = function() { self.$menu.off( self.transEndEventName ); self._resetMenu(); }; this.$menu.removeClass( 'dl-menuopen' ); this.$menu.addClass( 'dl-menu-toggle' ); this.$trigger.removeClass( 'dl-active' ); if( this.supportTransitions ) { this.$menu.on( this.transEndEventName, onTransitionEndFn ); } else { onTransitionEndFn.call(); } this.open = false; }, openMenu : function() { if( !this.open ) { this._openMenu(); } }, _openMenu : function() { var self = this; // clicking somewhere else makes the menu close $body.off( 'click' ).on( 'click.dlmenu', function() { self._closeMenu() ; } ); this.$menu.addClass( 'dl-menuopen dl-menu-toggle' ).on( this.transEndEventName, function() { $( this ).removeClass( 'dl-menu-toggle' ); } ); this.$trigger.addClass( 'dl-active' ); this.open = true; }, // resets the menu to its original state (first level of options) _resetMenu : function() { this.$menu.removeClass( 'dl-subview' ); this.$menuitems.removeClass( 'dl-subview dl-subviewopen' ); } }; var logError = function( message ) { if ( window.console ) { window.console.error( message ); } }; $.fn.dlmenu = function( options ) { if ( typeof options === 'string' ) { var args = Array.prototype.slice.call( arguments, 1 ); this.each(function() { var instance = $.data( this, 'dlmenu' ); if ( !instance ) { logError( "cannot call methods on dlmenu prior to initialization; " + "attempted to call method '" + options + "'" ); return; } if ( !$.isFunction( instance[options] ) || options.charAt(0) === "_" ) { logError( "no such method '" + options + "' for dlmenu instance" ); return; } instance[ options ].apply( instance, args ); }); } else { this.each(function() { var instance = $.data( this, 'dlmenu' ); if ( instance ) { instance._init(); } else { instance = $.data( this, 'dlmenu', new $.DLMenu( options, this ) ); } }); } return this; }; } )( jQuery, window ); ================================================ FILE: assets/js/plugins/jquery.fitvids.js ================================================ /*global jQuery */ /*jshint multistr:true, browser:true */ /*! * FitVids 1.0 * * Copyright 2011, Chris Coyier - http://css-tricks.com + Dave Rupert - http://daverupert.com * Credit to Thierry Koblentz - http://www.alistapart.com/articles/creating-intrinsic-ratios-for-video/ * Released under the WTFPL license - http://sam.zoy.org/wtfpl/ * * Date: Thu Sept 01 18:00:00 2011 -0500 */ (function( $ ){ "use strict"; $.fn.fitVids = function( options ) { var settings = { customSelector: null }; var div = document.createElement('div'), ref = document.getElementsByTagName('base')[0] || document.getElementsByTagName('script')[0]; div.className = 'fit-vids-style'; div.innerHTML = '­'; ref.parentNode.insertBefore(div,ref); if ( options ) { $.extend( settings, options ); } return this.each(function(){ var selectors = [ "iframe[src*='player.vimeo.com']", "iframe[src*='www.youtube.com']", "iframe[src*='www.youtube-nocookie.com']", "iframe[src*='www.kickstarter.com']", "object", "embed" ]; if (settings.customSelector) { selectors.push(settings.customSelector); } var $allVideos = $(this).find(selectors.join(',')); $allVideos.each(function(){ var $this = $(this); if (this.tagName.toLowerCase() === 'embed' && $this.parent('object').length || $this.parent('.fluid-width-video-wrapper').length) { return; } var height = ( this.tagName.toLowerCase() === 'object' || ($this.attr('height') && !isNaN(parseInt($this.attr('height'), 10))) ) ? parseInt($this.attr('height'), 10) : $this.height(), width = !isNaN(parseInt($this.attr('width'), 10)) ? parseInt($this.attr('width'), 10) : $this.width(), aspectRatio = height / width; if(!$this.attr('id')){ var videoID = 'fitvid' + Math.floor(Math.random()*999999); $this.attr('id', videoID); } $this.wrap('
    ').parent('.fluid-width-video-wrapper').css('padding-top', (aspectRatio * 100)+"%"); $this.removeAttr('height').removeAttr('width'); }); }); }; })( jQuery ); ================================================ FILE: assets/js/plugins/jquery.magnific-popup.js ================================================ /*! Magnific Popup - v0.9.3 - 2013-07-16 * http://dimsemenov.com/plugins/magnific-popup/ * Copyright (c) 2013 Dmitry Semenov; */ ;(function($) { /*>>core*/ /** * * Magnific Popup Core JS file * */ /** * Private static constants */ var CLOSE_EVENT = 'Close', BEFORE_CLOSE_EVENT = 'BeforeClose', AFTER_CLOSE_EVENT = 'AfterClose', BEFORE_APPEND_EVENT = 'BeforeAppend', MARKUP_PARSE_EVENT = 'MarkupParse', OPEN_EVENT = 'Open', CHANGE_EVENT = 'Change', NS = 'mfp', EVENT_NS = '.' + NS, READY_CLASS = 'mfp-ready', REMOVING_CLASS = 'mfp-removing', PREVENT_CLOSE_CLASS = 'mfp-prevent-close'; /** * Private vars */ var mfp, // As we have only one instance of MagnificPopup object, we define it locally to not to use 'this' MagnificPopup = function(){}, _isJQ = !!(window.jQuery), _prevStatus, _window = $(window), _body, _document, _prevContentType, _wrapClasses, _currPopupType; /** * Private functions */ var _mfpOn = function(name, f) { mfp.ev.on(NS + name + EVENT_NS, f); }, _getEl = function(className, appendTo, html, raw) { var el = document.createElement('div'); el.className = 'mfp-'+className; if(html) { el.innerHTML = html; } if(!raw) { el = $(el); if(appendTo) { el.appendTo(appendTo); } } else if(appendTo) { appendTo.appendChild(el); } return el; }, _mfpTrigger = function(e, data) { mfp.ev.triggerHandler(NS + e, data); if(mfp.st.callbacks) { // converts "mfpEventName" to "eventName" callback and triggers it if it's present e = e.charAt(0).toLowerCase() + e.slice(1); if(mfp.st.callbacks[e]) { mfp.st.callbacks[e].apply(mfp, $.isArray(data) ? data : [data]); } } }, _setFocus = function() { (mfp.st.focus ? mfp.content.find(mfp.st.focus).eq(0) : mfp.wrap).trigger('focus'); }, _getCloseBtn = function(type) { if(type !== _currPopupType || !mfp.currTemplate.closeBtn) { mfp.currTemplate.closeBtn = $( mfp.st.closeMarkup.replace('%title%', mfp.st.tClose ) ); _currPopupType = type; } return mfp.currTemplate.closeBtn; }, // Initialize Magnific Popup only when called at least once _checkInstance = function() { if(!$.magnificPopup.instance) { mfp = new MagnificPopup(); mfp.init(); $.magnificPopup.instance = mfp; } }, // Check to close popup or not // "target" is an element that was clicked _checkIfClose = function(target) { if($(target).hasClass(PREVENT_CLOSE_CLASS)) { return; } var closeOnContent = mfp.st.closeOnContentClick; var closeOnBg = mfp.st.closeOnBgClick; if(closeOnContent && closeOnBg) { return true; } else { // We close the popup if click is on close button or on preloader. Or if there is no content. if(!mfp.content || $(target).hasClass('mfp-close') || (mfp.preloader && target === mfp.preloader[0]) ) { return true; } // if click is outside the content if( (target !== mfp.content[0] && !$.contains(mfp.content[0], target)) ) { if(closeOnBg) { // last check, if the clicked element is in DOM, (in case it's removed onclick) if( $.contains(document, target) ) { return true; } } } else if(closeOnContent) { return true; } } return false; }, // CSS transition detection, http://stackoverflow.com/questions/7264899/detect-css-transitions-using-javascript-and-without-modernizr supportsTransitions = function() { var s = document.createElement('p').style, // 's' for style. better to create an element if body yet to exist v = ['ms','O','Moz','Webkit']; // 'v' for vendor if( s['transition'] !== undefined ) { return true; } while( v.length ) { if( v.pop() + 'Transition' in s ) { return true; } } return false; }; /** * Public functions */ MagnificPopup.prototype = { constructor: MagnificPopup, /** * Initializes Magnific Popup plugin. * This function is triggered only once when $.fn.magnificPopup or $.magnificPopup is executed */ init: function() { var appVersion = navigator.appVersion; mfp.isIE7 = appVersion.indexOf("MSIE 7.") !== -1; mfp.isIE8 = appVersion.indexOf("MSIE 8.") !== -1; mfp.isLowIE = mfp.isIE7 || mfp.isIE8; mfp.isAndroid = (/android/gi).test(appVersion); mfp.isIOS = (/iphone|ipad|ipod/gi).test(appVersion); mfp.supportsTransition = supportsTransitions(); // We disable fixed positioned lightbox on devices that don't handle it nicely. // If you know a better way of detecting this - let me know. mfp.probablyMobile = (mfp.isAndroid || mfp.isIOS || /(Opera Mini)|Kindle|webOS|BlackBerry|(Opera Mobi)|(Windows Phone)|IEMobile/i.test(navigator.userAgent) ); _body = $(document.body); _document = $(document); mfp.popupsCache = {}; }, /** * Opens popup * @param data [description] */ open: function(data) { var i; if(data.isObj === false) { // convert jQuery collection to array to avoid conflicts later mfp.items = data.items.toArray(); mfp.index = 0; var items = data.items, item; for(i = 0; i < items.length; i++) { item = items[i]; if(item.parsed) { item = item.el[0]; } if(item === data.el[0]) { mfp.index = i; break; } } } else { mfp.items = $.isArray(data.items) ? data.items : [data.items]; mfp.index = data.index || 0; } // if popup is already opened - we just update the content if(mfp.isOpen) { mfp.updateItemHTML(); return; } mfp.types = []; _wrapClasses = ''; if(data.mainEl && data.mainEl.length) { mfp.ev = data.mainEl.eq(0); } else { mfp.ev = _document; } if(data.key) { if(!mfp.popupsCache[data.key]) { mfp.popupsCache[data.key] = {}; } mfp.currTemplate = mfp.popupsCache[data.key]; } else { mfp.currTemplate = {}; } mfp.st = $.extend(true, {}, $.magnificPopup.defaults, data ); mfp.fixedContentPos = mfp.st.fixedContentPos === 'auto' ? !mfp.probablyMobile : mfp.st.fixedContentPos; if(mfp.st.modal) { mfp.st.closeOnContentClick = false; mfp.st.closeOnBgClick = false; mfp.st.showCloseBtn = false; mfp.st.enableEscapeKey = false; } // Building markup // main containers are created only once if(!mfp.bgOverlay) { // Dark overlay mfp.bgOverlay = _getEl('bg').on('click'+EVENT_NS, function() { mfp.close(); }); mfp.wrap = _getEl('wrap').attr('tabindex', -1).on('click'+EVENT_NS, function(e) { if(_checkIfClose(e.target)) { mfp.close(); } }); mfp.container = _getEl('container', mfp.wrap); } mfp.contentContainer = _getEl('content'); if(mfp.st.preloader) { mfp.preloader = _getEl('preloader', mfp.container, mfp.st.tLoading); } // Initializing modules var modules = $.magnificPopup.modules; for(i = 0; i < modules.length; i++) { var n = modules[i]; n = n.charAt(0).toUpperCase() + n.slice(1); mfp['init'+n].call(mfp); } _mfpTrigger('BeforeOpen'); if(mfp.st.showCloseBtn) { // Close button if(!mfp.st.closeBtnInside) { mfp.wrap.append( _getCloseBtn() ); } else { _mfpOn(MARKUP_PARSE_EVENT, function(e, template, values, item) { values.close_replaceWith = _getCloseBtn(item.type); }); _wrapClasses += ' mfp-close-btn-in'; } } if(mfp.st.alignTop) { _wrapClasses += ' mfp-align-top'; } if(mfp.fixedContentPos) { mfp.wrap.css({ overflow: mfp.st.overflowY, overflowX: 'hidden', overflowY: mfp.st.overflowY }); } else { mfp.wrap.css({ top: _window.scrollTop(), position: 'absolute' }); } if( mfp.st.fixedBgPos === false || (mfp.st.fixedBgPos === 'auto' && !mfp.fixedContentPos) ) { mfp.bgOverlay.css({ height: _document.height(), position: 'absolute' }); } if(mfp.st.enableEscapeKey) { // Close on ESC key _document.on('keyup' + EVENT_NS, function(e) { if(e.keyCode === 27) { mfp.close(); } }); } _window.on('resize' + EVENT_NS, function() { mfp.updateSize(); }); if(!mfp.st.closeOnContentClick) { _wrapClasses += ' mfp-auto-cursor'; } if(_wrapClasses) mfp.wrap.addClass(_wrapClasses); // this triggers recalculation of layout, so we get it once to not to trigger twice var windowHeight = mfp.wH = _window.height(); var windowStyles = {}; if( mfp.fixedContentPos ) { if(mfp._hasScrollBar(windowHeight)){ var s = mfp._getScrollbarSize(); if(s) { windowStyles.paddingRight = s; } } } if(mfp.fixedContentPos) { if(!mfp.isIE7) { windowStyles.overflow = 'hidden'; } else { // ie7 double-scroll bug $('body, html').css('overflow', 'hidden'); } } var classesToadd = mfp.st.mainClass; if(mfp.isIE7) { classesToadd += ' mfp-ie7'; } if(classesToadd) { mfp._addClassToMFP( classesToadd ); } // add content mfp.updateItemHTML(); _mfpTrigger('BuildControls'); // remove scrollbar, add padding e.t.c $('html').css(windowStyles); // add everything to DOM mfp.bgOverlay.add(mfp.wrap).prependTo( document.body ); // Save last focused element mfp._lastFocusedEl = document.activeElement; // Wait for next cycle to allow CSS transition setTimeout(function() { if(mfp.content) { mfp._addClassToMFP(READY_CLASS); _setFocus(); } else { // if content is not defined (not loaded e.t.c) we add class only for BG mfp.bgOverlay.addClass(READY_CLASS); } // Trap the focus in popup _document.on('focusin' + EVENT_NS, function (e) { if( e.target !== mfp.wrap[0] && !$.contains(mfp.wrap[0], e.target) ) { _setFocus(); return false; } }); }, 16); mfp.isOpen = true; mfp.updateSize(windowHeight); _mfpTrigger(OPEN_EVENT); }, /** * Closes the popup */ close: function() { if(!mfp.isOpen) return; _mfpTrigger(BEFORE_CLOSE_EVENT); mfp.isOpen = false; // for CSS3 animation if(mfp.st.removalDelay && !mfp.isLowIE && mfp.supportsTransition ) { mfp._addClassToMFP(REMOVING_CLASS); setTimeout(function() { mfp._close(); }, mfp.st.removalDelay); } else { mfp._close(); } }, /** * Helper for close() function */ _close: function() { _mfpTrigger(CLOSE_EVENT); var classesToRemove = REMOVING_CLASS + ' ' + READY_CLASS + ' '; mfp.bgOverlay.detach(); mfp.wrap.detach(); mfp.container.empty(); if(mfp.st.mainClass) { classesToRemove += mfp.st.mainClass + ' '; } mfp._removeClassFromMFP(classesToRemove); if(mfp.fixedContentPos) { var windowStyles = {paddingRight: ''}; if(mfp.isIE7) { $('body, html').css('overflow', ''); } else { windowStyles.overflow = ''; } $('html').css(windowStyles); } _document.off('keyup' + EVENT_NS + ' focusin' + EVENT_NS); mfp.ev.off(EVENT_NS); // clean up DOM elements that aren't removed mfp.wrap.attr('class', 'mfp-wrap').removeAttr('style'); mfp.bgOverlay.attr('class', 'mfp-bg'); mfp.container.attr('class', 'mfp-container'); // remove close button from target element if(mfp.st.showCloseBtn && (!mfp.st.closeBtnInside || mfp.currTemplate[mfp.currItem.type] === true)) { if(mfp.currTemplate.closeBtn) mfp.currTemplate.closeBtn.detach(); } if(mfp._lastFocusedEl) { $(mfp._lastFocusedEl).trigger('focus'); // put tab focus back } mfp.currItem = null; mfp.content = null; mfp.currTemplate = null; mfp.prevHeight = 0; _mfpTrigger(AFTER_CLOSE_EVENT); }, updateSize: function(winHeight) { if(mfp.isIOS) { // fixes iOS nav bars https://github.com/dimsemenov/Magnific-Popup/issues/2 var zoomLevel = document.documentElement.clientWidth / window.innerWidth; var height = window.innerHeight * zoomLevel; mfp.wrap.css('height', height); mfp.wH = height; } else { mfp.wH = winHeight || _window.height(); } // Fixes #84: popup incorrectly positioned with position:relative on body if(!mfp.fixedContentPos) { mfp.wrap.css('height', mfp.wH); } _mfpTrigger('Resize'); }, /** * Set content of popup based on current index */ updateItemHTML: function() { var item = mfp.items[mfp.index]; // Detach and perform modifications mfp.contentContainer.detach(); if(mfp.content) mfp.content.detach(); if(!item.parsed) { item = mfp.parseEl( mfp.index ); } var type = item.type; _mfpTrigger('BeforeChange', [mfp.currItem ? mfp.currItem.type : '', type]); // BeforeChange event works like so: // _mfpOn('BeforeChange', function(e, prevType, newType) { }); mfp.currItem = item; if(!mfp.currTemplate[type]) { var markup = mfp.st[type] ? mfp.st[type].markup : false; // allows to modify markup _mfpTrigger('FirstMarkupParse', markup); if(markup) { mfp.currTemplate[type] = $(markup); } else { // if there is no markup found we just define that template is parsed mfp.currTemplate[type] = true; } } if(_prevContentType && _prevContentType !== item.type) { mfp.container.removeClass('mfp-'+_prevContentType+'-holder'); } var newContent = mfp['get' + type.charAt(0).toUpperCase() + type.slice(1)](item, mfp.currTemplate[type]); mfp.appendContent(newContent, type); item.preloaded = true; _mfpTrigger(CHANGE_EVENT, item); _prevContentType = item.type; // Append container back after its content changed mfp.container.prepend(mfp.contentContainer); _mfpTrigger('AfterChange'); }, /** * Set HTML content of popup */ appendContent: function(newContent, type) { mfp.content = newContent; if(newContent) { if(mfp.st.showCloseBtn && mfp.st.closeBtnInside && mfp.currTemplate[type] === true) { // if there is no markup, we just append close button element inside if(!mfp.content.find('.mfp-close').length) { mfp.content.append(_getCloseBtn()); } } else { mfp.content = newContent; } } else { mfp.content = ''; } _mfpTrigger(BEFORE_APPEND_EVENT); mfp.container.addClass('mfp-'+type+'-holder'); mfp.contentContainer.append(mfp.content); }, /** * Creates Magnific Popup data object based on given data * @param {int} index Index of item to parse */ parseEl: function(index) { var item = mfp.items[index], type = item.type; if(item.tagName) { item = { el: $(item) }; } else { item = { data: item, src: item.src }; } if(item.el) { var types = mfp.types; // check for 'mfp-TYPE' class for(var i = 0; i < types.length; i++) { if( item.el.hasClass('mfp-'+types[i]) ) { type = types[i]; break; } } item.src = item.el.attr('data-mfp-src'); if(!item.src) { item.src = item.el.attr('href'); } } item.type = type || mfp.st.type || 'inline'; item.index = index; item.parsed = true; mfp.items[index] = item; _mfpTrigger('ElementParse', item); return mfp.items[index]; }, /** * Initializes single popup or a group of popups */ addGroup: function(el, options) { var eHandler = function(e) { e.mfpEl = this; mfp._openClick(e, el, options); }; if(!options) { options = {}; } var eName = 'click.magnificPopup'; options.mainEl = el; if(options.items) { options.isObj = true; el.off(eName).on(eName, eHandler); } else { options.isObj = false; if(options.delegate) { el.off(eName).on(eName, options.delegate , eHandler); } else { options.items = el; el.off(eName).on(eName, eHandler); } } }, _openClick: function(e, el, options) { var midClick = options.midClick !== undefined ? options.midClick : $.magnificPopup.defaults.midClick; if(!midClick && ( e.which === 2 || e.ctrlKey || e.metaKey ) ) { return; } var disableOn = options.disableOn !== undefined ? options.disableOn : $.magnificPopup.defaults.disableOn; if(disableOn) { if($.isFunction(disableOn)) { if( !disableOn.call(mfp) ) { return true; } } else { // else it's number if( _window.width() < disableOn ) { return true; } } } if(e.type) { e.preventDefault(); // This will prevent popup from closing if element is inside and popup is already opened if(mfp.isOpen) { e.stopPropagation(); } } options.el = $(e.mfpEl); if(options.delegate) { options.items = el.find(options.delegate); } mfp.open(options); }, /** * Updates text on preloader */ updateStatus: function(status, text) { if(mfp.preloader) { if(_prevStatus !== status) { mfp.container.removeClass('mfp-s-'+_prevStatus); } if(!text && status === 'loading') { text = mfp.st.tLoading; } var data = { status: status, text: text }; // allows to modify status _mfpTrigger('UpdateStatus', data); status = data.status; text = data.text; mfp.preloader.html(text); mfp.preloader.find('a').on('click', function(e) { e.stopImmediatePropagation(); }); mfp.container.addClass('mfp-s-'+status); _prevStatus = status; } }, /* "Private" helpers that aren't private at all */ _addClassToMFP: function(cName) { mfp.bgOverlay.addClass(cName); mfp.wrap.addClass(cName); }, _removeClassFromMFP: function(cName) { this.bgOverlay.removeClass(cName); mfp.wrap.removeClass(cName); }, _hasScrollBar: function(winHeight) { return ( (mfp.isIE7 ? _document.height() : document.body.scrollHeight) > (winHeight || _window.height()) ); }, _parseMarkup: function(template, values, item) { var arr; if(item.data) { values = $.extend(item.data, values); } _mfpTrigger(MARKUP_PARSE_EVENT, [template, values, item] ); $.each(values, function(key, value) { if(value === undefined || value === false) { return true; } arr = key.split('_'); if(arr.length > 1) { var el = template.find(EVENT_NS + '-'+arr[0]); if(el.length > 0) { var attr = arr[1]; if(attr === 'replaceWith') { if(el[0] !== value[0]) { el.replaceWith(value); } } else if(attr === 'img') { if(el.is('img')) { el.attr('src', value); } else { el.replaceWith( '' ); } } else { el.attr(arr[1], value); } } } else { template.find(EVENT_NS + '-'+key).html(value); } }); }, _getScrollbarSize: function() { // thx David if(mfp.scrollbarSize === undefined) { var scrollDiv = document.createElement("div"); scrollDiv.id = "mfp-sbm"; scrollDiv.style.cssText = 'width: 99px; height: 99px; overflow: scroll; position: absolute; top: -9999px;'; document.body.appendChild(scrollDiv); mfp.scrollbarSize = scrollDiv.offsetWidth - scrollDiv.clientWidth; document.body.removeChild(scrollDiv); } return mfp.scrollbarSize; } }; /* MagnificPopup core prototype end */ /** * Public static functions */ $.magnificPopup = { instance: null, proto: MagnificPopup.prototype, modules: [], open: function(options, index) { _checkInstance(); if(!options) options = {}; options.isObj = true; options.index = index || 0; return this.instance.open(options); }, close: function() { return $.magnificPopup.instance.close(); }, registerModule: function(name, module) { if(module.options) { $.magnificPopup.defaults[name] = module.options; } $.extend(this.proto, module.proto); this.modules.push(name); }, defaults: { // Info about options is in docs: // http://dimsemenov.com/plugins/magnific-popup/documentation.html#options disableOn: 0, key: null, midClick: false, mainClass: '', preloader: true, focus: '', // CSS selector of input to focus after popup is opened closeOnContentClick: false, closeOnBgClick: true, closeBtnInside: true, showCloseBtn: true, enableEscapeKey: true, modal: false, alignTop: false, removalDelay: 0, fixedContentPos: 'auto', fixedBgPos: 'auto', overflowY: 'auto', closeMarkup: '', tClose: 'Close (Esc)', tLoading: 'Loading...' } }; $.fn.magnificPopup = function(options) { _checkInstance(); var jqEl = $(this); // We call some API method of first param is a string if (typeof options === "string" ) { if(options === 'open') { var items, itemOpts = _isJQ ? jqEl.data('magnificPopup') : jqEl[0].magnificPopup, index = parseInt(arguments[1], 10) || 0; if(itemOpts.items) { items = itemOpts.items[index]; } else { items = jqEl; if(itemOpts.delegate) { items = items.find(itemOpts.delegate); } items = items.eq( index ); } mfp._openClick({mfpEl:items}, jqEl, itemOpts); } else { if(mfp.isOpen) mfp[options].apply(mfp, Array.prototype.slice.call(arguments, 1)); } } else { /* * As Zepto doesn't support .data() method for objects * and it works only in normal browsers * we assign "options" object directly to the DOM element. FTW! */ if(_isJQ) { jqEl.data('magnificPopup', options); } else { jqEl[0].magnificPopup = options; } mfp.addGroup(jqEl, options); } return jqEl; }; //Quick benchmark /* var start = performance.now(), i, rounds = 1000; for(i = 0; i < rounds; i++) { } console.log('Test #1:', performance.now() - start); start = performance.now(); for(i = 0; i < rounds; i++) { } console.log('Test #2:', performance.now() - start); */ /*>>core*/ /*>>inline*/ var INLINE_NS = 'inline', _hiddenClass, _inlinePlaceholder, _lastInlineElement, _putInlineElementsBack = function() { if(_lastInlineElement) { _inlinePlaceholder.after( _lastInlineElement.addClass(_hiddenClass) ).detach(); _lastInlineElement = null; } }; $.magnificPopup.registerModule(INLINE_NS, { options: { hiddenClass: 'hide', // will be appended with `mfp-` prefix markup: '', tNotFound: 'Content not found' }, proto: { initInline: function() { mfp.types.push(INLINE_NS); _mfpOn(CLOSE_EVENT+'.'+INLINE_NS, function() { _putInlineElementsBack(); }); }, getInline: function(item, template) { _putInlineElementsBack(); if(item.src) { var inlineSt = mfp.st.inline, el = $(item.src); if(el.length) { // If target element has parent - we replace it with placeholder and put it back after popup is closed var parent = el[0].parentNode; if(parent && parent.tagName) { if(!_inlinePlaceholder) { _hiddenClass = inlineSt.hiddenClass; _inlinePlaceholder = _getEl(_hiddenClass); _hiddenClass = 'mfp-'+_hiddenClass; } // replace target inline element with placeholder _lastInlineElement = el.after(_inlinePlaceholder).detach().removeClass(_hiddenClass); } mfp.updateStatus('ready'); } else { mfp.updateStatus('error', inlineSt.tNotFound); el = $('
    '); } item.inlineElement = el; return el; } mfp.updateStatus('ready'); mfp._parseMarkup(template, {}, item); return template; } } }); /*>>inline*/ /*>>ajax*/ var AJAX_NS = 'ajax', _ajaxCur, _removeAjaxCursor = function() { if(_ajaxCur) { _body.removeClass(_ajaxCur); } }; $.magnificPopup.registerModule(AJAX_NS, { options: { settings: null, cursor: 'mfp-ajax-cur', tError: 'The content could not be loaded.' }, proto: { initAjax: function() { mfp.types.push(AJAX_NS); _ajaxCur = mfp.st.ajax.cursor; _mfpOn(CLOSE_EVENT+'.'+AJAX_NS, function() { _removeAjaxCursor(); if(mfp.req) { mfp.req.abort(); } }); }, getAjax: function(item) { if(_ajaxCur) _body.addClass(_ajaxCur); mfp.updateStatus('loading'); var opts = $.extend({ url: item.src, success: function(data, textStatus, jqXHR) { var temp = { data:data, xhr:jqXHR }; _mfpTrigger('ParseAjax', temp); mfp.appendContent( $(temp.data), AJAX_NS ); item.finished = true; _removeAjaxCursor(); _setFocus(); setTimeout(function() { mfp.wrap.addClass(READY_CLASS); }, 16); mfp.updateStatus('ready'); _mfpTrigger('AjaxContentAdded'); }, error: function() { _removeAjaxCursor(); item.finished = item.loadError = true; mfp.updateStatus('error', mfp.st.ajax.tError.replace('%url%', item.src)); } }, mfp.st.ajax.settings); mfp.req = $.ajax(opts); return ''; } } }); /*>>ajax*/ /*>>image*/ var _imgInterval, _getTitle = function(item) { if(item.data && item.data.title !== undefined) return item.data.title; var src = mfp.st.image.titleSrc; if(src) { if($.isFunction(src)) { return src.call(mfp, item); } else if(item.el) { return item.el.attr(src) || ''; } } return ''; }; $.magnificPopup.registerModule('image', { options: { markup: '
    '+ '
    '+ '
    '+ '
    '+ '
    '+ '
    '+ '
    '+ '
    ', cursor: 'mfp-zoom-out-cur', titleSrc: 'title', verticalFit: true, tError: 'The image could not be loaded.' }, proto: { initImage: function() { var imgSt = mfp.st.image, ns = '.image'; mfp.types.push('image'); _mfpOn(OPEN_EVENT+ns, function() { if(mfp.currItem.type === 'image' && imgSt.cursor) { _body.addClass(imgSt.cursor); } }); _mfpOn(CLOSE_EVENT+ns, function() { if(imgSt.cursor) { _body.removeClass(imgSt.cursor); } _window.off('resize' + EVENT_NS); }); _mfpOn('Resize'+ns, mfp.resizeImage); if(mfp.isLowIE) { _mfpOn('AfterChange', mfp.resizeImage); } }, resizeImage: function() { var item = mfp.currItem; if(!item.img) return; if(mfp.st.image.verticalFit) { var decr = 0; // fix box-sizing in ie7/8 if(mfp.isLowIE) { decr = parseInt(item.img.css('padding-top'), 10) + parseInt(item.img.css('padding-bottom'),10); } item.img.css('max-height', mfp.wH-decr); } }, _onImageHasSize: function(item) { if(item.img) { item.hasSize = true; if(_imgInterval) { clearInterval(_imgInterval); } item.isCheckingImgSize = false; _mfpTrigger('ImageHasSize', item); if(item.imgHidden) { if(mfp.content) mfp.content.removeClass('mfp-loading'); item.imgHidden = false; } } }, /** * Function that loops until the image has size to display elements that rely on it asap */ findImageSize: function(item) { var counter = 0, img = item.img[0], mfpSetInterval = function(delay) { if(_imgInterval) { clearInterval(_imgInterval); } // decelerating interval that checks for size of an image _imgInterval = setInterval(function() { if(img.naturalWidth > 0) { mfp._onImageHasSize(item); return; } if(counter > 200) { clearInterval(_imgInterval); } counter++; if(counter === 3) { mfpSetInterval(10); } else if(counter === 40) { mfpSetInterval(50); } else if(counter === 100) { mfpSetInterval(500); } }, delay); }; mfpSetInterval(1); }, getImage: function(item, template) { var guard = 0, // image load complete handler onLoadComplete = function() { if(item) { if (item.img[0].complete) { item.img.off('.mfploader'); if(item === mfp.currItem){ mfp._onImageHasSize(item); mfp.updateStatus('ready'); } item.hasSize = true; item.loaded = true; _mfpTrigger('ImageLoadComplete'); } else { // if image complete check fails 200 times (20 sec), we assume that there was an error. guard++; if(guard < 200) { setTimeout(onLoadComplete,100); } else { onLoadError(); } } } }, // image error handler onLoadError = function() { if(item) { item.img.off('.mfploader'); if(item === mfp.currItem){ mfp._onImageHasSize(item); mfp.updateStatus('error', imgSt.tError.replace('%url%', item.src) ); } item.hasSize = true; item.loaded = true; item.loadError = true; } }, imgSt = mfp.st.image; var el = template.find('.mfp-img'); if(el.length) { var img = new Image(); img.className = 'mfp-img'; item.img = $(img).on('load.mfploader', onLoadComplete).on('error.mfploader', onLoadError); img.src = item.src; // without clone() "error" event is not firing when IMG is replaced by new IMG // TODO: find a way to avoid such cloning if(el.is('img')) { item.img = item.img.clone(); } if(item.img[0].naturalWidth > 0) { item.hasSize = true; } } mfp._parseMarkup(template, { title: _getTitle(item), img_replaceWith: item.img }, item); mfp.resizeImage(); if(item.hasSize) { if(_imgInterval) clearInterval(_imgInterval); if(item.loadError) { template.addClass('mfp-loading'); mfp.updateStatus('error', imgSt.tError.replace('%url%', item.src) ); } else { template.removeClass('mfp-loading'); mfp.updateStatus('ready'); } return template; } mfp.updateStatus('loading'); item.loading = true; if(!item.hasSize) { item.imgHidden = true; template.addClass('mfp-loading'); mfp.findImageSize(item); } return template; } } }); /*>>image*/ /*>>zoom*/ var hasMozTransform, getHasMozTransform = function() { if(hasMozTransform === undefined) { hasMozTransform = document.createElement('p').style.MozTransform !== undefined; } return hasMozTransform; }; $.magnificPopup.registerModule('zoom', { options: { enabled: false, easing: 'ease-in-out', duration: 300, opener: function(element) { return element.is('img') ? element : element.find('img'); } }, proto: { initZoom: function() { var zoomSt = mfp.st.zoom, ns = '.zoom'; if(!zoomSt.enabled || !mfp.supportsTransition) { return; } var duration = zoomSt.duration, getElToAnimate = function(image) { var newImg = image.clone().removeAttr('style').removeAttr('class').addClass('mfp-animated-image'), transition = 'all '+(zoomSt.duration/1000)+'s ' + zoomSt.easing, cssObj = { position: 'fixed', zIndex: 9999, left: 0, top: 0, '-webkit-backface-visibility': 'hidden' }, t = 'transition'; cssObj['-webkit-'+t] = cssObj['-moz-'+t] = cssObj['-o-'+t] = cssObj[t] = transition; newImg.css(cssObj); return newImg; }, showMainContent = function() { mfp.content.css('visibility', 'visible'); }, openTimeout, animatedImg; _mfpOn('BuildControls'+ns, function() { if(mfp._allowZoom()) { clearTimeout(openTimeout); mfp.content.css('visibility', 'hidden'); // Basically, all code below does is clones existing image, puts in on top of the current one and animated it image = mfp._getItemToZoom(); if(!image) { showMainContent(); return; } animatedImg = getElToAnimate(image); animatedImg.css( mfp._getOffset() ); mfp.wrap.append(animatedImg); openTimeout = setTimeout(function() { animatedImg.css( mfp._getOffset( true ) ); openTimeout = setTimeout(function() { showMainContent(); setTimeout(function() { animatedImg.remove(); image = animatedImg = null; _mfpTrigger('ZoomAnimationEnded'); }, 16); // avoid blink when switching images }, duration); // this timeout equals animation duration }, 16); // by adding this timeout we avoid short glitch at the beginning of animation // Lots of timeouts... } }); _mfpOn(BEFORE_CLOSE_EVENT+ns, function() { if(mfp._allowZoom()) { clearTimeout(openTimeout); mfp.st.removalDelay = duration; if(!image) { image = mfp._getItemToZoom(); if(!image) { return; } animatedImg = getElToAnimate(image); } animatedImg.css( mfp._getOffset(true) ); mfp.wrap.append(animatedImg); mfp.content.css('visibility', 'hidden'); setTimeout(function() { animatedImg.css( mfp._getOffset() ); }, 16); } }); _mfpOn(CLOSE_EVENT+ns, function() { if(mfp._allowZoom()) { showMainContent(); if(animatedImg) { animatedImg.remove(); } } }); }, _allowZoom: function() { return mfp.currItem.type === 'image'; }, _getItemToZoom: function() { if(mfp.currItem.hasSize) { return mfp.currItem.img; } else { return false; } }, // Get element postion relative to viewport _getOffset: function(isLarge) { var el; if(isLarge) { el = mfp.currItem.img; } else { el = mfp.st.zoom.opener(mfp.currItem.el || mfp.currItem); } var offset = el.offset(); var paddingTop = parseInt(el.css('padding-top'),10); var paddingBottom = parseInt(el.css('padding-bottom'),10); offset.top -= ( $(window).scrollTop() - paddingTop ); /* Animating left + top + width/height looks glitchy in Firefox, but perfect in Chrome. And vice-versa. */ var obj = { width: el.width(), // fix Zepto height+padding issue height: (_isJQ ? el.innerHeight() : el[0].offsetHeight) - paddingBottom - paddingTop }; // I hate to do this, but there is no another option if( getHasMozTransform() ) { obj['-moz-transform'] = obj['transform'] = 'translate(' + offset.left + 'px,' + offset.top + 'px)'; } else { obj.left = offset.left; obj.top = offset.top; } return obj; } } }); /*>>zoom*/ /*>>iframe*/ var IFRAME_NS = 'iframe', _emptyPage = '//about:blank', _fixIframeBugs = function(isShowing) { if(mfp.currTemplate[IFRAME_NS]) { var el = mfp.currTemplate[IFRAME_NS].find('iframe'); if(el.length) { // reset src after the popup is closed to avoid "video keeps playing after popup is closed" bug if(!isShowing) { el[0].src = _emptyPage; } // IE8 black screen bug fix if(mfp.isIE8) { el.css('display', isShowing ? 'block' : 'none'); } } } }; $.magnificPopup.registerModule(IFRAME_NS, { options: { markup: '
    '+ '
    '+ ''+ '
    ', srcAction: 'iframe_src', // we don't care and support only one default type of URL by default patterns: { youtube: { index: 'youtube.com', id: 'v=', src: '//www.youtube.com/embed/%id%?autoplay=1' }, vimeo: { index: 'vimeo.com/', id: '/', src: '//player.vimeo.com/video/%id%?autoplay=1' }, gmaps: { index: '//maps.google.', src: '%id%&output=embed' } } }, proto: { initIframe: function() { mfp.types.push(IFRAME_NS); _mfpOn('BeforeChange', function(e, prevType, newType) { if(prevType !== newType) { if(prevType === IFRAME_NS) { _fixIframeBugs(); // iframe if removed } else if(newType === IFRAME_NS) { _fixIframeBugs(true); // iframe is showing } }// else { // iframe source is switched, don't do anything //} }); _mfpOn(CLOSE_EVENT + '.' + IFRAME_NS, function() { _fixIframeBugs(); }); }, getIframe: function(item, template) { var embedSrc = item.src; var iframeSt = mfp.st.iframe; $.each(iframeSt.patterns, function() { if(embedSrc.indexOf( this.index ) > -1) { if(this.id) { if(typeof this.id === 'string') { embedSrc = embedSrc.substr(embedSrc.lastIndexOf(this.id)+this.id.length, embedSrc.length); } else { embedSrc = this.id.call( this, embedSrc ); } } embedSrc = this.src.replace('%id%', embedSrc ); return false; // break; } }); var dataObj = {}; if(iframeSt.srcAction) { dataObj[iframeSt.srcAction] = embedSrc; } mfp._parseMarkup(template, dataObj, item); mfp.updateStatus('ready'); return template; } } }); /*>>iframe*/ /*>>gallery*/ /** * Get looped index depending on number of slides */ var _getLoopedId = function(index) { var numSlides = mfp.items.length; if(index > numSlides - 1) { return index - numSlides; } else if(index < 0) { return numSlides + index; } return index; }, _replaceCurrTotal = function(text, curr, total) { return text.replace('%curr%', curr + 1).replace('%total%', total); }; $.magnificPopup.registerModule('gallery', { options: { enabled: false, arrowMarkup: '', preload: [0,2], navigateByImgClick: true, arrows: true, tPrev: 'Previous (Left arrow key)', tNext: 'Next (Right arrow key)', tCounter: '%curr% of %total%' }, proto: { initGallery: function() { var gSt = mfp.st.gallery, ns = '.mfp-gallery', supportsFastClick = Boolean($.fn.mfpFastClick); mfp.direction = true; // true - next, false - prev if(!gSt || !gSt.enabled ) return false; _wrapClasses += ' mfp-gallery'; _mfpOn(OPEN_EVENT+ns, function() { if(gSt.navigateByImgClick) { mfp.wrap.on('click'+ns, '.mfp-img', function() { if(mfp.items.length > 1) { mfp.next(); return false; } }); } _document.on('keydown'+ns, function(e) { if (e.keyCode === 37) { mfp.prev(); } else if (e.keyCode === 39) { mfp.next(); } }); }); _mfpOn('UpdateStatus'+ns, function(e, data) { if(data.text) { data.text = _replaceCurrTotal(data.text, mfp.currItem.index, mfp.items.length); } }); _mfpOn(MARKUP_PARSE_EVENT+ns, function(e, element, values, item) { var l = mfp.items.length; values.counter = l > 1 ? _replaceCurrTotal(gSt.tCounter, item.index, l) : ''; }); _mfpOn('BuildControls' + ns, function() { if(mfp.items.length > 1 && gSt.arrows && !mfp.arrowLeft) { var markup = gSt.arrowMarkup, arrowLeft = mfp.arrowLeft = $( markup.replace('%title%', gSt.tPrev).replace('%dir%', 'left') ).addClass(PREVENT_CLOSE_CLASS), arrowRight = mfp.arrowRight = $( markup.replace('%title%', gSt.tNext).replace('%dir%', 'right') ).addClass(PREVENT_CLOSE_CLASS); var eName = supportsFastClick ? 'mfpFastClick' : 'click'; arrowLeft[eName](function() { mfp.prev(); }); arrowRight[eName](function() { mfp.next(); }); // Polyfill for :before and :after (adds elements with classes mfp-a and mfp-b) if(mfp.isIE7) { _getEl('b', arrowLeft[0], false, true); _getEl('a', arrowLeft[0], false, true); _getEl('b', arrowRight[0], false, true); _getEl('a', arrowRight[0], false, true); } mfp.container.append(arrowLeft.add(arrowRight)); } }); _mfpOn(CHANGE_EVENT+ns, function() { if(mfp._preloadTimeout) clearTimeout(mfp._preloadTimeout); mfp._preloadTimeout = setTimeout(function() { mfp.preloadNearbyImages(); mfp._preloadTimeout = null; }, 16); }); _mfpOn(CLOSE_EVENT+ns, function() { _document.off(ns); mfp.wrap.off('click'+ns); if(mfp.arrowLeft && supportsFastClick) { mfp.arrowLeft.add(mfp.arrowRight).destroyMfpFastClick(); } mfp.arrowRight = mfp.arrowLeft = null; }); }, next: function() { mfp.direction = true; mfp.index = _getLoopedId(mfp.index + 1); mfp.updateItemHTML(); }, prev: function() { mfp.direction = false; mfp.index = _getLoopedId(mfp.index - 1); mfp.updateItemHTML(); }, goTo: function(newIndex) { mfp.direction = (newIndex >= mfp.index); mfp.index = newIndex; mfp.updateItemHTML(); }, preloadNearbyImages: function() { var p = mfp.st.gallery.preload, preloadBefore = Math.min(p[0], mfp.items.length), preloadAfter = Math.min(p[1], mfp.items.length), i; for(i = 1; i <= (mfp.direction ? preloadAfter : preloadBefore); i++) { mfp._preloadItem(mfp.index+i); } for(i = 1; i <= (mfp.direction ? preloadBefore : preloadAfter); i++) { mfp._preloadItem(mfp.index-i); } }, _preloadItem: function(index) { index = _getLoopedId(index); if(mfp.items[index].preloaded) { return; } var item = mfp.items[index]; if(!item.parsed) { item = mfp.parseEl( index ); } _mfpTrigger('LazyLoad', item); if(item.type === 'image') { item.img = $('').on('load.mfploader', function() { item.hasSize = true; }).on('error.mfploader', function() { item.hasSize = true; item.loadError = true; _mfpTrigger('LazyLoadError', item); }).attr('src', item.src); } item.preloaded = true; } } }); /* Touch Support that might be implemented some day addSwipeGesture: function() { var startX, moved, multipleTouches; return; var namespace = '.mfp', addEventNames = function(pref, down, move, up, cancel) { mfp._tStart = pref + down + namespace; mfp._tMove = pref + move + namespace; mfp._tEnd = pref + up + namespace; mfp._tCancel = pref + cancel + namespace; }; if(window.navigator.msPointerEnabled) { addEventNames('MSPointer', 'Down', 'Move', 'Up', 'Cancel'); } else if('ontouchstart' in window) { addEventNames('touch', 'start', 'move', 'end', 'cancel'); } else { return; } _window.on(mfp._tStart, function(e) { var oE = e.originalEvent; multipleTouches = moved = false; startX = oE.pageX || oE.changedTouches[0].pageX; }).on(mfp._tMove, function(e) { if(e.originalEvent.touches.length > 1) { multipleTouches = e.originalEvent.touches.length; } else { //e.preventDefault(); moved = true; } }).on(mfp._tEnd + ' ' + mfp._tCancel, function(e) { if(moved && !multipleTouches) { var oE = e.originalEvent, diff = startX - (oE.pageX || oE.changedTouches[0].pageX); if(diff > 20) { mfp.next(); } else if(diff < -20) { mfp.prev(); } } }); }, */ /*>>gallery*/ /*>>retina*/ var RETINA_NS = 'retina'; $.magnificPopup.registerModule(RETINA_NS, { options: { replaceSrc: function(item) { return item.src.replace(/\.\w+$/, function(m) { return '@2x' + m; }); }, ratio: 1 // Function or number. Set to 1 to disable. }, proto: { initRetina: function() { if(window.devicePixelRatio > 1) { var st = mfp.st.retina, ratio = st.ratio; ratio = !isNaN(ratio) ? ratio : ratio(); if(ratio > 1) { _mfpOn('ImageHasSize' + '.' + RETINA_NS, function(e, item) { item.img.css({ 'max-width': item.img[0].naturalWidth / ratio, 'width': '100%' }); }); _mfpOn('ElementParse' + '.' + RETINA_NS, function(e, item) { item.src = st.replaceSrc(item, ratio); }); } } } } }); /*>>retina*/ /*>>fastclick*/ /** * FastClick event implementation. (removes 300ms delay on touch devices) * Based on https://developers.google.com/mobile/articles/fast_buttons * * You may use it outside the Magnific Popup by calling just: * * $('.your-el').mfpFastClick(function() { * console.log('Clicked!'); * }); * * To unbind: * $('.your-el').destroyMfpFastClick(); * * * Note that it's a very basic and simple implementation, it blocks ghost click on the same element where it was bound. * If you need something more advanced, use plugin by FT Labs https://github.com/ftlabs/fastclick * */ (function() { var ghostClickDelay = 1000, supportsTouch = 'ontouchstart' in window, unbindTouchMove = function() { _window.off('touchmove'+ns+' touchend'+ns); }, eName = 'mfpFastClick', ns = '.'+eName; // As Zepto.js doesn't have an easy way to add custom events (like jQuery), so we implement it in this way $.fn.mfpFastClick = function(callback) { return $(this).each(function() { var elem = $(this), lock; if( supportsTouch ) { var timeout, startX, startY, pointerMoved, point, numPointers; elem.on('touchstart' + ns, function(e) { pointerMoved = false; numPointers = 1; point = e.originalEvent ? e.originalEvent.touches[0] : e.touches[0]; startX = point.clientX; startY = point.clientY; _window.on('touchmove'+ns, function(e) { point = e.originalEvent ? e.originalEvent.touches : e.touches; numPointers = point.length; point = point[0]; if (Math.abs(point.clientX - startX) > 10 || Math.abs(point.clientY - startY) > 10) { pointerMoved = true; unbindTouchMove(); } }).on('touchend'+ns, function(e) { unbindTouchMove(); if(pointerMoved || numPointers > 1) { return; } lock = true; e.preventDefault(); clearTimeout(timeout); timeout = setTimeout(function() { lock = false; }, ghostClickDelay); callback(); }); }); } elem.on('click' + ns, function() { if(!lock) { callback(); } }); }); }; $.fn.destroyMfpFastClick = function() { $(this).off('touchstart' + ns + ' click' + ns); if(supportsTouch) _window.off('touchmove'+ns+' touchend'+ns); }; })(); /*>>fastclick*/ })(window.jQuery || window.Zepto); ================================================ FILE: assets/js/plugins/respond.js ================================================ /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */ /*! NOTE: If you're already including a window.matchMedia polyfill via Modernizr or otherwise, you don't need this part */ window.matchMedia = window.matchMedia || (function( doc, undefined ) { "use strict"; var bool, docElem = doc.documentElement, refNode = docElem.firstElementChild || docElem.firstChild, // fakeBody required for fakeBody = doc.createElement( "body" ), div = doc.createElement( "div" ); div.id = "mq-test-1"; div.style.cssText = "position:absolute;top:-100em"; fakeBody.style.background = "none"; fakeBody.appendChild(div); return function(q){ div.innerHTML = "­"; docElem.insertBefore( fakeBody, refNode ); bool = div.offsetWidth === 42; docElem.removeChild( fakeBody ); return { matches: bool, media: q }; }; }( document )); /*! Respond.js v1.1.0: min/max-width media query polyfill. (c) Scott Jehl. MIT/GPLv2 Lic. j.mp/respondjs */ (function( win ){ "use strict"; //exposed namespace var respond = {}; win.respond = respond; //define update even in native-mq-supporting browsers, to avoid errors respond.update = function(){}; //expose media query support flag for external use respond.mediaQueriesSupported = win.matchMedia && win.matchMedia( "only all" ).matches; //if media queries are supported, exit here if( respond.mediaQueriesSupported ){ return; } //define vars var doc = win.document, docElem = doc.documentElement, mediastyles = [], rules = [], appendedEls = [], parsedSheets = {}, resizeThrottle = 30, head = doc.getElementsByTagName( "head" )[0] || docElem, base = doc.getElementsByTagName( "base" )[0], links = head.getElementsByTagName( "link" ), requestQueue = [], //loop stylesheets, send text content to translate ripCSS = function(){ for( var i = 0; i < links.length; i++ ){ var sheet = links[ i ], href = sheet.href, media = sheet.media, isCSS = sheet.rel && sheet.rel.toLowerCase() === "stylesheet"; //only links plz and prevent re-parsing if( !!href && isCSS && !parsedSheets[ href ] ){ // selectivizr exposes css through the rawCssText expando if (sheet.styleSheet && sheet.styleSheet.rawCssText) { translate( sheet.styleSheet.rawCssText, href, media ); parsedSheets[ href ] = true; } else { if( (!/^([a-zA-Z:]*\/\/)/.test( href ) && !base) || href.replace( RegExp.$1, "" ).split( "/" )[0] === win.location.host ){ requestQueue.push( { href: href, media: media } ); } } } } makeRequests(); }, //recurse through request queue, get css text makeRequests = function(){ if( requestQueue.length ){ var thisRequest = requestQueue.shift(); ajax( thisRequest.href, function( styles ){ translate( styles, thisRequest.href, thisRequest.media ); parsedSheets[ thisRequest.href ] = true; // by wrapping recursive function call in setTimeout // we prevent "Stack overflow" error in IE7 win.setTimeout(function(){ makeRequests(); },0); } ); } }, //find media blocks in css text, convert to style blocks translate = function( styles, href, media ){ var qs = styles.match( /@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi ), ql = qs && qs.length || 0; //try to get CSS path href = href.substring( 0, href.lastIndexOf( "/" ) ); var repUrls = function( css ){ return css.replace( /(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g, "$1" + href + "$2$3" ); }, useMedia = !ql && media; //if path exists, tack on trailing slash if( href.length ){ href += "/"; } //if no internal queries exist, but media attr does, use that //note: this currently lacks support for situations where a media attr is specified on a link AND //its associated stylesheet has internal CSS media queries. //In those cases, the media attribute will currently be ignored. if( useMedia ){ ql = 1; } for( var i = 0; i < ql; i++ ){ var fullq, thisq, eachq, eql; //media attr if( useMedia ){ fullq = media; rules.push( repUrls( styles ) ); } //parse for styles else{ fullq = qs[ i ].match( /@media *([^\{]+)\{([\S\s]+?)$/ ) && RegExp.$1; rules.push( RegExp.$2 && repUrls( RegExp.$2 ) ); } eachq = fullq.split( "," ); eql = eachq.length; for( var j = 0; j < eql; j++ ){ thisq = eachq[ j ]; mediastyles.push( { media : thisq.split( "(" )[ 0 ].match( /(only\s+)?([a-zA-Z]+)\s?/ ) && RegExp.$2 || "all", rules : rules.length - 1, hasquery : thisq.indexOf("(") > -1, minw : thisq.match( /\(\s*min\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/ ) && parseFloat( RegExp.$1 ) + ( RegExp.$2 || "" ), maxw : thisq.match( /\(\s*max\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/ ) && parseFloat( RegExp.$1 ) + ( RegExp.$2 || "" ) } ); } } applyMedia(); }, lastCall, resizeDefer, // returns the value of 1em in pixels getEmValue = function() { var ret, div = doc.createElement('div'), body = doc.body, fakeUsed = false; div.style.cssText = "position:absolute;font-size:1em;width:1em"; if( !body ){ body = fakeUsed = doc.createElement( "body" ); body.style.background = "none"; } body.appendChild( div ); docElem.insertBefore( body, docElem.firstChild ); ret = div.offsetWidth; if( fakeUsed ){ docElem.removeChild( body ); } else { body.removeChild( div ); } //also update eminpx before returning ret = eminpx = parseFloat(ret); return ret; }, //cached container for 1em value, populated the first time it's needed eminpx, //enable/disable styles applyMedia = function( fromResize ){ var name = "clientWidth", docElemProp = docElem[ name ], currWidth = doc.compatMode === "CSS1Compat" && docElemProp || doc.body[ name ] || docElemProp, styleBlocks = {}, lastLink = links[ links.length-1 ], now = (new Date()).getTime(); //throttle resize calls if( fromResize && lastCall && now - lastCall < resizeThrottle ){ win.clearTimeout( resizeDefer ); resizeDefer = win.setTimeout( applyMedia, resizeThrottle ); return; } else { lastCall = now; } for( var i in mediastyles ){ if( mediastyles.hasOwnProperty( i ) ){ var thisstyle = mediastyles[ i ], min = thisstyle.minw, max = thisstyle.maxw, minnull = min === null, maxnull = max === null, em = "em"; if( !!min ){ min = parseFloat( min ) * ( min.indexOf( em ) > -1 ? ( eminpx || getEmValue() ) : 1 ); } if( !!max ){ max = parseFloat( max ) * ( max.indexOf( em ) > -1 ? ( eminpx || getEmValue() ) : 1 ); } // if there's no media query at all (the () part), or min or max is not null, and if either is present, they're true if( !thisstyle.hasquery || ( !minnull || !maxnull ) && ( minnull || currWidth >= min ) && ( maxnull || currWidth <= max ) ){ if( !styleBlocks[ thisstyle.media ] ){ styleBlocks[ thisstyle.media ] = []; } styleBlocks[ thisstyle.media ].push( rules[ thisstyle.rules ] ); } } } //remove any existing respond style element(s) for( var j in appendedEls ){ if( appendedEls.hasOwnProperty( j ) ){ if( appendedEls[ j ] && appendedEls[ j ].parentNode === head ){ head.removeChild( appendedEls[ j ] ); } } } //inject active styles, grouped by media type for( var k in styleBlocks ){ if( styleBlocks.hasOwnProperty( k ) ){ var ss = doc.createElement( "style" ), css = styleBlocks[ k ].join( "\n" ); ss.type = "text/css"; ss.media = k; //originally, ss was appended to a documentFragment and sheets were appended in bulk. //this caused crashes in IE in a number of circumstances, such as when the HTML element had a bg image set, so appending beforehand seems best. Thanks to @dvelyk for the initial research on this one! head.insertBefore( ss, lastLink.nextSibling ); if ( ss.styleSheet ){ ss.styleSheet.cssText = css; } else { ss.appendChild( doc.createTextNode( css ) ); } //push to appendedEls to track for later removal appendedEls.push( ss ); } } }, //tweaked Ajax functions from Quirksmode ajax = function( url, callback ) { var req = xmlHttp(); if (!req){ return; } req.open( "GET", url, true ); req.onreadystatechange = function () { if ( req.readyState !== 4 || req.status !== 200 && req.status !== 304 ){ return; } callback( req.responseText ); }; if ( req.readyState === 4 ){ return; } req.send( null ); }, //define ajax obj xmlHttp = (function() { var xmlhttpmethod = false; try { xmlhttpmethod = new win.XMLHttpRequest(); } catch( e ){ xmlhttpmethod = new win.ActiveXObject( "Microsoft.XMLHTTP" ); } return function(){ return xmlhttpmethod; }; })(); //translate CSS ripCSS(); //expose update for re-running respond later on respond.update = ripCSS; //adjust on resize function callMedia(){ applyMedia( true ); } if( win.addEventListener ){ win.addEventListener( "resize", callMedia, false ); } else if( win.attachEvent ){ win.attachEvent( "onresize", callMedia ); } })(this); ================================================ FILE: assets/less/coderay.less ================================================ .CodeRay { font-family: @code-font; .font(12); color: #d0d0d0; margin-bottom: 1.5em; .rounded(3px); } .CodeRay .code pre { margin: 0; padding: 1em; background-color: #272822; border: 1px solid darken(@body-color, 5); } div.CodeRay { } span.CodeRay { white-space: pre; border: 0px; padding: 2px } table.CodeRay { border-collapse: collapse; width: 100%; padding: 2px } table.CodeRay td { padding: 1em 0.5em; vertical-align: top; } .CodeRay .line-numbers, .CodeRay .no { color: #8f908a; text-align: right; } .CodeRay .line-numbers a { color: #8f908a; } .CodeRay .line-numbers tt { font-weight: bold } .CodeRay .line-numbers .highlighted { color: red } .CodeRay .line { display: block; float: left; width: 100%; } .CodeRay span.line-numbers { padding: 0 24px 0 4px; } .CodeRay .code { width: 100% } ol.CodeRay { font-size: 10pt } ol.CodeRay li { white-space: pre } .CodeRay .code pre { overflow: auto } .CodeRay .debug { color:white ! important; background:blue ! important; } .CodeRay .doctype, .CodeRay .key, .CodeRay .instance-variable { color: #f8f8f2; } .CodeRay .attribute-name { color: #a6e22e;} .CodeRay .symbol, .CodeRay .integer, .CodeRay .float { color: #ff658b; } .CodeRay .string { color: #2dc900; } .CodeRay .keyword { color: #66d9ef; } .CodeRay .function, .CodeRay .class { color: #a6e22e; } .CodeRay .regexp, .CodeRay .constant, .CodeRay .tag { color: #f92672; } .CodeRay .modifier, .CodeRay .predefined-constant { color: #ff84e4; } .CodeRay .comment { color: #75715e; } .CodeRay .error { color: #ecc; } .CodeRay .content { color: #e6db74; } .CodeRay .delimiter { color: #e6db74; } .CodeRay .inline { color: #e6db74; } ================================================ FILE: assets/less/dl-menu.less ================================================ .dl-menuwrapper { width: 100%; position: absolute; top: 0; left: 0; z-index: 1000; -webkit-perspective: 1000px; -moz-perspective: 1000px; perspective: 1000px; -webkit-perspective-origin: 50% 200%; -moz-perspective-origin: 50% 200%; perspective-origin: 50% 200%; @media @medium { position: fixed; max-width: 175px; top: 25px; left: 25px; } button { top: 0; left: 0; background: @comp-color; border: none; width: 48px; height: 45px; text-indent: -900em; overflow: hidden; position: relative; cursor: pointer; outline: none; .border-radius(0,3px,0,0); opacity: 0.6; @media @medium { .border-radius(3px,3px,3px,3px); } } button:hover, button.dl-active, ul { background: #aaa; } button:after { content: ''; position: absolute; width: 68%; height: 5px; background: @white; top: 10px; left: 16%; box-shadow: 0 10px 0 @white, 0 20px 0 @white; } button.dl-active { display: none; } ul { padding: 0; list-style: none; -webkit-transform-style: preserve-3d; -moz-transform-style: preserve-3d; transform-style: preserve-3d; } li { position: relative; h4 { margin:0; padding: 15px 20px 0; color: fade(@white,90); } p { margin: 0; padding: 15px 20px; .font-rem(14); color: fade(@white,80); a { display: inline; padding: 0; .font-rem(14); } } i { display: inline-block; width: 2em; } a { display: block; position: relative; padding: 15px 20px; .font-rem(14); line-height: 20px; font-weight: 300; color: @white; outline: none; } &.dl-back > a { padding-left: 30px; background: rgba(0,0,0,0.2); } &.dl-back:after, > a:not(:only-child):after { position: absolute; top: 0; line-height: 50px; font-family: 'fontawesome'; color: @white; speak: none; -webkit-font-smoothing: antialiased; content: "\f105"; } &.dl-back:after { left: 10px; color: rgba(212,204,198,0.5); -webkit-transform: rotate(180deg); -moz-transform: rotate(180deg); transform: rotate(180deg); } > a:after { right: 10px; color: rgba(0,0,0,0.15); } } .dl-menu { margin: 0; position: absolute; width: 100%; max-width: 400px; overflow-y: auto; max-height: 600px; opacity: 0; pointer-events: none; box-shadow: 0 12px 24px fade(@black,35); -webkit-transform: translateY(10px); -moz-transform: translateY(10px); transform: translateY(10px); -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; backface-visibility: hidden; @media @medium { .border-radius(3px,3px,3px,3px); max-height: 650px; } } .dl-menu.dl-menu-toggle { -webkit-transition: all 0.3s ease; -moz-transition: all 0.3s ease; transition: all 0.3s ease; } .dl-menu.dl-menuopen { opacity: 1; pointer-events: auto; -webkit-transform: translateY(0px); -moz-transform: translateY(0px); transform: translateY(0px); } .dl-submenu { .rounded(3px); box-shadow: 0 12px 24px fade(@black,35); } /* Hide the inner submenus */ li .dl-submenu { display: none; } } /* When a submenu is openend, we will hide all li siblings. For that we give a class to the parent menu called "dl-subview". We also hide the submenu link. The opened submenu will get the class "dl-subviewopen". All this is done for any sub-level being entered. */ .dl-menu.dl-subview li, .dl-menu.dl-subview li.dl-subviewopen > a, .dl-menu.dl-subview li.dl-subview > a { display: none; } .dl-menu.dl-subview li.dl-subview, .dl-menu.dl-subview li.dl-subview .dl-submenu, .dl-menu.dl-subview li.dl-subviewopen, .dl-menu.dl-subview li.dl-subviewopen > .dl-submenu, .dl-menu.dl-subview li.dl-subviewopen > .dl-submenu > li { display: block; } /* Animation classes for moving out and in */ .dl-menu.dl-animate-out { -webkit-animation: MenuAnimOut 0.4s ease; -moz-animation: MenuAnimOut 0.4s ease; animation: MenuAnimOut 0.4s ease; } @-webkit-keyframes MenuAnimOut { 100% { -webkit-transform: translateZ(300px); opacity: 0; } } @-moz-keyframes MenuAnimOut { 100% { -moz-transform: translateZ(300px); opacity: 0; } } @keyframes MenuAnimOut { 100% { transform: translateZ(300px); opacity: 0; } } .dl-menu.dl-animate-in { -webkit-animation: MenuAnimIn 0.4s ease; -moz-animation: MenuAnimIn 0.4s ease; animation: MenuAnimIn 0.4s ease; } @-webkit-keyframes MenuAnimIn { 0% { -webkit-transform: translateZ(300px); opacity: 0; } 100% { -webkit-transform: translateZ(0px); opacity: 1; } } @-moz-keyframes MenuAnimIn { 0% { -moz-transform: translateZ(300px); opacity: 0; } 100% { -moz-transform: translateZ(0px); opacity: 1; } } @keyframes MenuAnimIn { 0% { transform: translateZ(300px); opacity: 0; } 100% { transform: translateZ(0px); opacity: 1; } } .dl-menuwrapper > .dl-submenu.dl-animate-in { -webkit-animation: SubMenuAnimIn 0.4s ease; -moz-animation: SubMenuAnimIn 0.4s ease; animation: SubMenuAnimIn 0.4s ease; } @-webkit-keyframes SubMenuAnimIn { 0% { -webkit-transform: translateZ(-300px); opacity: 0; } 100% { -webkit-transform: translateZ(0px); opacity: 1; } } @-moz-keyframes SubMenuAnimIn { 0% { -moz-transform: translateZ(-300px); opacity: 0; } 100% { -moz-transform: translateZ(0px); opacity: 1; } } @keyframes SubMenuAnimIn { 0% { transform: translateZ(-300px); opacity: 0; } 100% { transform: translateZ(0px); opacity: 1; } } .dl-menuwrapper > .dl-submenu.dl-animate-out { -webkit-animation: SubMenuAnimOut 0.4s ease; -moz-animation: SubMenuAnimOut 0.4s ease; animation: SubMenuAnimOut 0.4s ease; } @-webkit-keyframes SubMenuAnimOut { 0% { -webkit-transform: translateZ(0px); opacity: 1; } 100% { -webkit-transform: translateZ(-300px); opacity: 0; } } @-moz-keyframes SubMenuAnimOut { 0% { -moz-transform: translateZ(0px); opacity: 1; } 100% { -moz-transform: translateZ(-300px); opacity: 0; } } @keyframes SubMenuAnimOut { 0% { transform: translateZ(0px); opacity: 1; } 100% { transform: translateZ(-300px); opacity: 0; } } /* No Touch Fallback */ .no-touch .dl-menuwrapper li a:hover { background: rgba(255,248,213,0.1); } /* No JS Fallback */ .no-js { .dl-trigger { display: none; } .dl-menuwrapper .dl-menu { position: relative; opacity: 1; pointer-events: auto; -webkit-transform: none; -moz-transform: none; transform: none; } .dl-menuwrapper li .dl-submenu { display: block; } .dl-menuwrapper li.dl-back { display: none; } .dl-menuwrapper li > a:not(:only-child) { background: rgba(0,0,0,0.1); } .dl-menuwrapper li > a:not(:only-child):after { content: ''; } } // Menu Color .dl-menuwrapper button:hover, .dl-menuwrapper button.dl-active, .dl-menuwrapper ul { background: @comp-color; } // Fix for IE .dl-menu li { display: none } .dl-menuopen li { display: block } ================================================ FILE: assets/less/elements.less ================================================ hr { display: block; margin: 1em 0; padding: 0; height: 1px; border: 0; border-top: 1px solid #ccc; border-bottom: 1px solid #fff; } // Figures and images // -------------------------------------------------- figure { margin: 0; padding-top: 10px; padding-bottom: 10px; .clearfix(); img { margin-bottom: 10px; } a { img { .translate(0, 0); -webkit-transition-duration: 0.25s; -moz-transition-duration: 0.25s; -o-transition-duration: 0.25s; &:hover { .translate(0, -5px); .box-shadow(0 0 10px fade(@base-color, 20)); } } } &.half { @media @large { img { width: 310px; float: left; margin-right: 10px; } figcaption { clear: left; } } } &.third { @media @large { img { width: 200px; float: left; margin-right: 10px; } figcaption { clear: left; } } } } svg:not(:root) { overflow: hidden; } // Buttons // -------------------------------------------------- .btn { display: inline-block; margin-bottom: 20px; padding: 8px 20px; .font-rem(14); background-color: @primary; color: @white; border-width: 2px !important; border-style: solid !important; border-color: @primary; .rounded(3px); &:visited { color: @white; } &:hover { background-color: @white; color: @primary; } } .btn-success { background-color: @success; color: @white; border-color: @success; &:visited { color: @white; } &:hover { background-color: @white; color: @success; } } .btn-warning { background-color: @warning; color: @white; border-color: @warning; &:visited { color: @white; } &:hover { background-color: @white; color: @warning; } } .btn-danger { background-color: @danger; color: @white; border-color: @danger; &:visited { color: @white; } &:hover { background-color: @white; color: @danger; } } .btn-info { background-color: @info; color: @white; border-color: @info; &:visited { color: @white; } &:hover { background-color: @white; color: @info; } } // Well // -------------------------------------------------- .well { padding: 20px; border: 1px solid @comp-color; .rounded(4px); } // Animations // -------------------------------------------------- .animated{-webkit-animation-fill-mode:both;-moz-animation-fill-mode:both;-ms-animation-fill-mode:both;-o-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:1s;-moz-animation-duration:1s;-ms-animation-duration:1s;-o-animation-duration:1s;animation-duration:1s;}.animated.hinge{-webkit-animation-duration:2s;-moz-animation-duration:2s;-ms-animation-duration:2s;-o-animation-duration:2s;animation-duration:2s;}@-webkit-keyframes fadeIn { 0% {opacity: 0;} 100% {opacity: 1;} } @-moz-keyframes fadeIn { 0% {opacity: 0;} 100% {opacity: 1;} } @-o-keyframes fadeIn { 0% {opacity: 0;} 100% {opacity: 1;} } @keyframes fadeIn { 0% {opacity: 0;} 100% {opacity: 1;} } .fadeIn { -webkit-animation-name: fadeIn; -moz-animation-name: fadeIn; -o-animation-name: fadeIn; animation-name: fadeIn; } @-webkit-keyframes fadeInDown { 0% { opacity: 0; -webkit-transform: translateY(-20px); } 100% { opacity: 1; -webkit-transform: translateY(0); } } @-moz-keyframes fadeInDown { 0% { opacity: 0; -moz-transform: translateY(-20px); } 100% { opacity: 1; -moz-transform: translateY(0); } } @-o-keyframes fadeInDown { 0% { opacity: 0; -o-transform: translateY(-20px); } 100% { opacity: 1; -o-transform: translateY(0); } } @keyframes fadeInDown { 0% { opacity: 0; transform: translateY(-20px); } 100% { opacity: 1; transform: translateY(0); } } .fadeInDown { -webkit-animation-name: fadeInDown; -moz-animation-name: fadeInDown; -o-animation-name: fadeInDown; animation-name: fadeInDown; } @-webkit-keyframes fadeInDownBig { 0% { opacity: 0; -webkit-transform: translateY(-2000px); } 100% { opacity: 1; -webkit-transform: translateY(0); } } @-moz-keyframes fadeInDownBig { 0% { opacity: 0; -moz-transform: translateY(-2000px); } 100% { opacity: 1; -moz-transform: translateY(0); } } @-o-keyframes fadeInDownBig { 0% { opacity: 0; -o-transform: translateY(-2000px); } 100% { opacity: 1; -o-transform: translateY(0); } } @keyframes fadeInDownBig { 0% { opacity: 0; transform: translateY(-2000px); } 100% { opacity: 1; transform: translateY(0); } } .fadeInDownBig { -webkit-animation-name: fadeInDownBig; -moz-animation-name: fadeInDownBig; -o-animation-name: fadeInDownBig; animation-name: fadeInDownBig; } @-webkit-keyframes bounceIn { 0% { opacity: 0; -webkit-transform: scale(.3); } 50% { opacity: 1; -webkit-transform: scale(1.05); } 70% { -webkit-transform: scale(.9); } 100% { -webkit-transform: scale(1); } } @-moz-keyframes bounceIn { 0% { opacity: 0; -moz-transform: scale(.3); } 50% { opacity: 1; -moz-transform: scale(1.05); } 70% { -moz-transform: scale(.9); } 100% { -moz-transform: scale(1); } } @-o-keyframes bounceIn { 0% { opacity: 0; -o-transform: scale(.3); } 50% { opacity: 1; -o-transform: scale(1.05); } 70% { -o-transform: scale(.9); } 100% { -o-transform: scale(1); } } @keyframes bounceIn { 0% { opacity: 0; transform: scale(.3); } 50% { opacity: 1; transform: scale(1.05); } 70% { transform: scale(.9); } 100% { transform: scale(1); } } .bounceIn { -webkit-animation-name: bounceIn; -moz-animation-name: bounceIn; -o-animation-name: bounceIn; animation-name: bounceIn; } @-webkit-keyframes bounceInDown { 0% { opacity: 0; -webkit-transform: translateY(-2000px); } 60% { opacity: 1; -webkit-transform: translateY(30px); } 80% { -webkit-transform: translateY(-10px); } 100% { -webkit-transform: translateY(0); } } @-moz-keyframes bounceInDown { 0% { opacity: 0; -moz-transform: translateY(-2000px); } 60% { opacity: 1; -moz-transform: translateY(30px); } 80% { -moz-transform: translateY(-10px); } 100% { -moz-transform: translateY(0); } } @-o-keyframes bounceInDown { 0% { opacity: 0; -o-transform: translateY(-2000px); } 60% { opacity: 1; -o-transform: translateY(30px); } 80% { -o-transform: translateY(-10px); } 100% { -o-transform: translateY(0); } } @keyframes bounceInDown { 0% { opacity: 0; transform: translateY(-2000px); } 60% { opacity: 1; transform: translateY(30px); } 80% { transform: translateY(-10px); } 100% { transform: translateY(0); } } .bounceInDown { -webkit-animation-name: bounceInDown; -moz-animation-name: bounceInDown; -o-animation-name: bounceInDown; animation-name: bounceInDown; } @-webkit-keyframes drop { 0% { -webkit-transform: translateY(-500px); } 100% { -webkit-transform: translateY(0); } } @-moz-keyframes drop { 0% { -moz-transform: translateY(-500px); } 100% { -moz-transform: translateY(0); } } @-o-keyframes drop { 0% { -o-transform: translateY(-500px); } 100% { -o-transform: translateY(0); } } @keyframes drop { 0% { transform: translateY(-500px); } 100% { transform: translateY(0); } } .drop { -webkit-animation-name: drop; -moz-animation-name: drop; -o-animation-name: drop; animation-name: drop; } ================================================ FILE: assets/less/font-awesome/bordered-pulled.less ================================================ // Bordered & Pulled // ------------------------- .@{fa-css-prefix}-border { padding: .2em .25em .15em; border: solid .08em @fa-border-color; border-radius: .1em; } .pull-right { float: right; } .pull-left { float: left; } .@{fa-css-prefix} { &.pull-left { margin-right: .3em; } &.pull-right { margin-left: .3em; } } ================================================ FILE: assets/less/font-awesome/core.less ================================================ // Base Class Definition // ------------------------- .@{fa-css-prefix} { display: inline-block; font-family: FontAwesome; font-style: normal; font-weight: normal; line-height: 1; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } ================================================ FILE: assets/less/font-awesome/fixed-width.less ================================================ // Fixed Width Icons // ------------------------- .@{fa-css-prefix}-fw { width: (18em / 14); text-align: center; } ================================================ FILE: assets/less/font-awesome/font-awesome.less ================================================ /*! * Font Awesome 4.1.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) */ @import "variables.less"; @import "mixins.less"; @import "path.less"; @import "core.less"; @import "larger.less"; @import "fixed-width.less"; @import "list.less"; @import "bordered-pulled.less"; @import "spinning.less"; @import "rotated-flipped.less"; @import "stacked.less"; @import "icons.less"; ================================================ FILE: assets/less/font-awesome/icons.less ================================================ /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen readers do not read off random characters that represent icons */ .@{fa-css-prefix}-glass:before { content: @fa-var-glass; } .@{fa-css-prefix}-music:before { content: @fa-var-music; } .@{fa-css-prefix}-search:before { content: @fa-var-search; } .@{fa-css-prefix}-envelope-o:before { content: @fa-var-envelope-o; } .@{fa-css-prefix}-heart:before { content: @fa-var-heart; } .@{fa-css-prefix}-star:before { content: @fa-var-star; } .@{fa-css-prefix}-star-o:before { content: @fa-var-star-o; } .@{fa-css-prefix}-user:before { content: @fa-var-user; } .@{fa-css-prefix}-film:before { content: @fa-var-film; } .@{fa-css-prefix}-th-large:before { content: @fa-var-th-large; } .@{fa-css-prefix}-th:before { content: @fa-var-th; } .@{fa-css-prefix}-th-list:before { content: @fa-var-th-list; } .@{fa-css-prefix}-check:before { content: @fa-var-check; } .@{fa-css-prefix}-times:before { content: @fa-var-times; } .@{fa-css-prefix}-search-plus:before { content: @fa-var-search-plus; } .@{fa-css-prefix}-search-minus:before { content: @fa-var-search-minus; } .@{fa-css-prefix}-power-off:before { content: @fa-var-power-off; } .@{fa-css-prefix}-signal:before { content: @fa-var-signal; } .@{fa-css-prefix}-gear:before, .@{fa-css-prefix}-cog:before { content: @fa-var-cog; } .@{fa-css-prefix}-trash-o:before { content: @fa-var-trash-o; } .@{fa-css-prefix}-home:before { content: @fa-var-home; } .@{fa-css-prefix}-file-o:before { content: @fa-var-file-o; } .@{fa-css-prefix}-clock-o:before { content: @fa-var-clock-o; } .@{fa-css-prefix}-road:before { content: @fa-var-road; } .@{fa-css-prefix}-download:before { content: @fa-var-download; } .@{fa-css-prefix}-arrow-circle-o-down:before { content: @fa-var-arrow-circle-o-down; } .@{fa-css-prefix}-arrow-circle-o-up:before { content: @fa-var-arrow-circle-o-up; } .@{fa-css-prefix}-inbox:before { content: @fa-var-inbox; } .@{fa-css-prefix}-play-circle-o:before { content: @fa-var-play-circle-o; } .@{fa-css-prefix}-rotate-right:before, .@{fa-css-prefix}-repeat:before { content: @fa-var-repeat; } .@{fa-css-prefix}-refresh:before { content: @fa-var-refresh; } .@{fa-css-prefix}-list-alt:before { content: @fa-var-list-alt; } .@{fa-css-prefix}-lock:before { content: @fa-var-lock; } .@{fa-css-prefix}-flag:before { content: @fa-var-flag; } .@{fa-css-prefix}-headphones:before { content: @fa-var-headphones; } .@{fa-css-prefix}-volume-off:before { content: @fa-var-volume-off; } .@{fa-css-prefix}-volume-down:before { content: @fa-var-volume-down; } .@{fa-css-prefix}-volume-up:before { content: @fa-var-volume-up; } .@{fa-css-prefix}-qrcode:before { content: @fa-var-qrcode; } .@{fa-css-prefix}-barcode:before { content: @fa-var-barcode; } .@{fa-css-prefix}-tag:before { content: @fa-var-tag; } .@{fa-css-prefix}-tags:before { content: @fa-var-tags; } .@{fa-css-prefix}-book:before { content: @fa-var-book; } .@{fa-css-prefix}-bookmark:before { content: @fa-var-bookmark; } .@{fa-css-prefix}-print:before { content: @fa-var-print; } .@{fa-css-prefix}-camera:before { content: @fa-var-camera; } .@{fa-css-prefix}-font:before { content: @fa-var-font; } .@{fa-css-prefix}-bold:before { content: @fa-var-bold; } .@{fa-css-prefix}-italic:before { content: @fa-var-italic; } .@{fa-css-prefix}-text-height:before { content: @fa-var-text-height; } .@{fa-css-prefix}-text-width:before { content: @fa-var-text-width; } .@{fa-css-prefix}-align-left:before { content: @fa-var-align-left; } .@{fa-css-prefix}-align-center:before { content: @fa-var-align-center; } .@{fa-css-prefix}-align-right:before { content: @fa-var-align-right; } .@{fa-css-prefix}-align-justify:before { content: @fa-var-align-justify; } .@{fa-css-prefix}-list:before { content: @fa-var-list; } .@{fa-css-prefix}-dedent:before, .@{fa-css-prefix}-outdent:before { content: @fa-var-outdent; } .@{fa-css-prefix}-indent:before { content: @fa-var-indent; } .@{fa-css-prefix}-video-camera:before { content: @fa-var-video-camera; } .@{fa-css-prefix}-photo:before, .@{fa-css-prefix}-image:before, .@{fa-css-prefix}-picture-o:before { content: @fa-var-picture-o; } .@{fa-css-prefix}-pencil:before { content: @fa-var-pencil; } .@{fa-css-prefix}-map-marker:before { content: @fa-var-map-marker; } .@{fa-css-prefix}-adjust:before { content: @fa-var-adjust; } .@{fa-css-prefix}-tint:before { content: @fa-var-tint; } .@{fa-css-prefix}-edit:before, .@{fa-css-prefix}-pencil-square-o:before { content: @fa-var-pencil-square-o; } .@{fa-css-prefix}-share-square-o:before { content: @fa-var-share-square-o; } .@{fa-css-prefix}-check-square-o:before { content: @fa-var-check-square-o; } .@{fa-css-prefix}-arrows:before { content: @fa-var-arrows; } .@{fa-css-prefix}-step-backward:before { content: @fa-var-step-backward; } .@{fa-css-prefix}-fast-backward:before { content: @fa-var-fast-backward; } .@{fa-css-prefix}-backward:before { content: @fa-var-backward; } .@{fa-css-prefix}-play:before { content: @fa-var-play; } .@{fa-css-prefix}-pause:before { content: @fa-var-pause; } .@{fa-css-prefix}-stop:before { content: @fa-var-stop; } .@{fa-css-prefix}-forward:before { content: @fa-var-forward; } .@{fa-css-prefix}-fast-forward:before { content: @fa-var-fast-forward; } .@{fa-css-prefix}-step-forward:before { content: @fa-var-step-forward; } .@{fa-css-prefix}-eject:before { content: @fa-var-eject; } .@{fa-css-prefix}-chevron-left:before { content: @fa-var-chevron-left; } .@{fa-css-prefix}-chevron-right:before { content: @fa-var-chevron-right; } .@{fa-css-prefix}-plus-circle:before { content: @fa-var-plus-circle; } .@{fa-css-prefix}-minus-circle:before { content: @fa-var-minus-circle; } .@{fa-css-prefix}-times-circle:before { content: @fa-var-times-circle; } .@{fa-css-prefix}-check-circle:before { content: @fa-var-check-circle; } .@{fa-css-prefix}-question-circle:before { content: @fa-var-question-circle; } .@{fa-css-prefix}-info-circle:before { content: @fa-var-info-circle; } .@{fa-css-prefix}-crosshairs:before { content: @fa-var-crosshairs; } .@{fa-css-prefix}-times-circle-o:before { content: @fa-var-times-circle-o; } .@{fa-css-prefix}-check-circle-o:before { content: @fa-var-check-circle-o; } .@{fa-css-prefix}-ban:before { content: @fa-var-ban; } .@{fa-css-prefix}-arrow-left:before { content: @fa-var-arrow-left; } .@{fa-css-prefix}-arrow-right:before { content: @fa-var-arrow-right; } .@{fa-css-prefix}-arrow-up:before { content: @fa-var-arrow-up; } .@{fa-css-prefix}-arrow-down:before { content: @fa-var-arrow-down; } .@{fa-css-prefix}-mail-forward:before, .@{fa-css-prefix}-share:before { content: @fa-var-share; } .@{fa-css-prefix}-expand:before { content: @fa-var-expand; } .@{fa-css-prefix}-compress:before { content: @fa-var-compress; } .@{fa-css-prefix}-plus:before { content: @fa-var-plus; } .@{fa-css-prefix}-minus:before { content: @fa-var-minus; } .@{fa-css-prefix}-asterisk:before { content: @fa-var-asterisk; } .@{fa-css-prefix}-exclamation-circle:before { content: @fa-var-exclamation-circle; } .@{fa-css-prefix}-gift:before { content: @fa-var-gift; } .@{fa-css-prefix}-leaf:before { content: @fa-var-leaf; } .@{fa-css-prefix}-fire:before { content: @fa-var-fire; } .@{fa-css-prefix}-eye:before { content: @fa-var-eye; } .@{fa-css-prefix}-eye-slash:before { content: @fa-var-eye-slash; } .@{fa-css-prefix}-warning:before, .@{fa-css-prefix}-exclamation-triangle:before { content: @fa-var-exclamation-triangle; } .@{fa-css-prefix}-plane:before { content: @fa-var-plane; } .@{fa-css-prefix}-calendar:before { content: @fa-var-calendar; } .@{fa-css-prefix}-random:before { content: @fa-var-random; } .@{fa-css-prefix}-comment:before { content: @fa-var-comment; } .@{fa-css-prefix}-magnet:before { content: @fa-var-magnet; } .@{fa-css-prefix}-chevron-up:before { content: @fa-var-chevron-up; } .@{fa-css-prefix}-chevron-down:before { content: @fa-var-chevron-down; } .@{fa-css-prefix}-retweet:before { content: @fa-var-retweet; } .@{fa-css-prefix}-shopping-cart:before { content: @fa-var-shopping-cart; } .@{fa-css-prefix}-folder:before { content: @fa-var-folder; } .@{fa-css-prefix}-folder-open:before { content: @fa-var-folder-open; } .@{fa-css-prefix}-arrows-v:before { content: @fa-var-arrows-v; } .@{fa-css-prefix}-arrows-h:before { content: @fa-var-arrows-h; } .@{fa-css-prefix}-bar-chart-o:before { content: @fa-var-bar-chart-o; } .@{fa-css-prefix}-twitter-square:before { content: @fa-var-twitter-square; } .@{fa-css-prefix}-facebook-square:before { content: @fa-var-facebook-square; } .@{fa-css-prefix}-camera-retro:before { content: @fa-var-camera-retro; } .@{fa-css-prefix}-key:before { content: @fa-var-key; } .@{fa-css-prefix}-gears:before, .@{fa-css-prefix}-cogs:before { content: @fa-var-cogs; } .@{fa-css-prefix}-comments:before { content: @fa-var-comments; } .@{fa-css-prefix}-thumbs-o-up:before { content: @fa-var-thumbs-o-up; } .@{fa-css-prefix}-thumbs-o-down:before { content: @fa-var-thumbs-o-down; } .@{fa-css-prefix}-star-half:before { content: @fa-var-star-half; } .@{fa-css-prefix}-heart-o:before { content: @fa-var-heart-o; } .@{fa-css-prefix}-sign-out:before { content: @fa-var-sign-out; } .@{fa-css-prefix}-linkedin-square:before { content: @fa-var-linkedin-square; } .@{fa-css-prefix}-thumb-tack:before { content: @fa-var-thumb-tack; } .@{fa-css-prefix}-external-link:before { content: @fa-var-external-link; } .@{fa-css-prefix}-sign-in:before { content: @fa-var-sign-in; } .@{fa-css-prefix}-trophy:before { content: @fa-var-trophy; } .@{fa-css-prefix}-github-square:before { content: @fa-var-github-square; } .@{fa-css-prefix}-upload:before { content: @fa-var-upload; } .@{fa-css-prefix}-lemon-o:before { content: @fa-var-lemon-o; } .@{fa-css-prefix}-phone:before { content: @fa-var-phone; } .@{fa-css-prefix}-square-o:before { content: @fa-var-square-o; } .@{fa-css-prefix}-bookmark-o:before { content: @fa-var-bookmark-o; } .@{fa-css-prefix}-phone-square:before { content: @fa-var-phone-square; } .@{fa-css-prefix}-twitter:before { content: @fa-var-twitter; } .@{fa-css-prefix}-facebook:before { content: @fa-var-facebook; } .@{fa-css-prefix}-github:before { content: @fa-var-github; } .@{fa-css-prefix}-unlock:before { content: @fa-var-unlock; } .@{fa-css-prefix}-credit-card:before { content: @fa-var-credit-card; } .@{fa-css-prefix}-rss:before { content: @fa-var-rss; } .@{fa-css-prefix}-hdd-o:before { content: @fa-var-hdd-o; } .@{fa-css-prefix}-bullhorn:before { content: @fa-var-bullhorn; } .@{fa-css-prefix}-bell:before { content: @fa-var-bell; } .@{fa-css-prefix}-certificate:before { content: @fa-var-certificate; } .@{fa-css-prefix}-hand-o-right:before { content: @fa-var-hand-o-right; } .@{fa-css-prefix}-hand-o-left:before { content: @fa-var-hand-o-left; } .@{fa-css-prefix}-hand-o-up:before { content: @fa-var-hand-o-up; } .@{fa-css-prefix}-hand-o-down:before { content: @fa-var-hand-o-down; } .@{fa-css-prefix}-arrow-circle-left:before { content: @fa-var-arrow-circle-left; } .@{fa-css-prefix}-arrow-circle-right:before { content: @fa-var-arrow-circle-right; } .@{fa-css-prefix}-arrow-circle-up:before { content: @fa-var-arrow-circle-up; } .@{fa-css-prefix}-arrow-circle-down:before { content: @fa-var-arrow-circle-down; } .@{fa-css-prefix}-globe:before { content: @fa-var-globe; } .@{fa-css-prefix}-wrench:before { content: @fa-var-wrench; } .@{fa-css-prefix}-tasks:before { content: @fa-var-tasks; } .@{fa-css-prefix}-filter:before { content: @fa-var-filter; } .@{fa-css-prefix}-briefcase:before { content: @fa-var-briefcase; } .@{fa-css-prefix}-arrows-alt:before { content: @fa-var-arrows-alt; } .@{fa-css-prefix}-group:before, .@{fa-css-prefix}-users:before { content: @fa-var-users; } .@{fa-css-prefix}-chain:before, .@{fa-css-prefix}-link:before { content: @fa-var-link; } .@{fa-css-prefix}-cloud:before { content: @fa-var-cloud; } .@{fa-css-prefix}-flask:before { content: @fa-var-flask; } .@{fa-css-prefix}-cut:before, .@{fa-css-prefix}-scissors:before { content: @fa-var-scissors; } .@{fa-css-prefix}-copy:before, .@{fa-css-prefix}-files-o:before { content: @fa-var-files-o; } .@{fa-css-prefix}-paperclip:before { content: @fa-var-paperclip; } .@{fa-css-prefix}-save:before, .@{fa-css-prefix}-floppy-o:before { content: @fa-var-floppy-o; } .@{fa-css-prefix}-square:before { content: @fa-var-square; } .@{fa-css-prefix}-navicon:before, .@{fa-css-prefix}-reorder:before, .@{fa-css-prefix}-bars:before { content: @fa-var-bars; } .@{fa-css-prefix}-list-ul:before { content: @fa-var-list-ul; } .@{fa-css-prefix}-list-ol:before { content: @fa-var-list-ol; } .@{fa-css-prefix}-strikethrough:before { content: @fa-var-strikethrough; } .@{fa-css-prefix}-underline:before { content: @fa-var-underline; } .@{fa-css-prefix}-table:before { content: @fa-var-table; } .@{fa-css-prefix}-magic:before { content: @fa-var-magic; } .@{fa-css-prefix}-truck:before { content: @fa-var-truck; } .@{fa-css-prefix}-pinterest:before { content: @fa-var-pinterest; } .@{fa-css-prefix}-pinterest-square:before { content: @fa-var-pinterest-square; } .@{fa-css-prefix}-google-plus-square:before { content: @fa-var-google-plus-square; } .@{fa-css-prefix}-google-plus:before { content: @fa-var-google-plus; } .@{fa-css-prefix}-money:before { content: @fa-var-money; } .@{fa-css-prefix}-caret-down:before { content: @fa-var-caret-down; } .@{fa-css-prefix}-caret-up:before { content: @fa-var-caret-up; } .@{fa-css-prefix}-caret-left:before { content: @fa-var-caret-left; } .@{fa-css-prefix}-caret-right:before { content: @fa-var-caret-right; } .@{fa-css-prefix}-columns:before { content: @fa-var-columns; } .@{fa-css-prefix}-unsorted:before, .@{fa-css-prefix}-sort:before { content: @fa-var-sort; } .@{fa-css-prefix}-sort-down:before, .@{fa-css-prefix}-sort-desc:before { content: @fa-var-sort-desc; } .@{fa-css-prefix}-sort-up:before, .@{fa-css-prefix}-sort-asc:before { content: @fa-var-sort-asc; } .@{fa-css-prefix}-envelope:before { content: @fa-var-envelope; } .@{fa-css-prefix}-linkedin:before { content: @fa-var-linkedin; } .@{fa-css-prefix}-rotate-left:before, .@{fa-css-prefix}-undo:before { content: @fa-var-undo; } .@{fa-css-prefix}-legal:before, .@{fa-css-prefix}-gavel:before { content: @fa-var-gavel; } .@{fa-css-prefix}-dashboard:before, .@{fa-css-prefix}-tachometer:before { content: @fa-var-tachometer; } .@{fa-css-prefix}-comment-o:before { content: @fa-var-comment-o; } .@{fa-css-prefix}-comments-o:before { content: @fa-var-comments-o; } .@{fa-css-prefix}-flash:before, .@{fa-css-prefix}-bolt:before { content: @fa-var-bolt; } .@{fa-css-prefix}-sitemap:before { content: @fa-var-sitemap; } .@{fa-css-prefix}-umbrella:before { content: @fa-var-umbrella; } .@{fa-css-prefix}-paste:before, .@{fa-css-prefix}-clipboard:before { content: @fa-var-clipboard; } .@{fa-css-prefix}-lightbulb-o:before { content: @fa-var-lightbulb-o; } .@{fa-css-prefix}-exchange:before { content: @fa-var-exchange; } .@{fa-css-prefix}-cloud-download:before { content: @fa-var-cloud-download; } .@{fa-css-prefix}-cloud-upload:before { content: @fa-var-cloud-upload; } .@{fa-css-prefix}-user-md:before { content: @fa-var-user-md; } .@{fa-css-prefix}-stethoscope:before { content: @fa-var-stethoscope; } .@{fa-css-prefix}-suitcase:before { content: @fa-var-suitcase; } .@{fa-css-prefix}-bell-o:before { content: @fa-var-bell-o; } .@{fa-css-prefix}-coffee:before { content: @fa-var-coffee; } .@{fa-css-prefix}-cutlery:before { content: @fa-var-cutlery; } .@{fa-css-prefix}-file-text-o:before { content: @fa-var-file-text-o; } .@{fa-css-prefix}-building-o:before { content: @fa-var-building-o; } .@{fa-css-prefix}-hospital-o:before { content: @fa-var-hospital-o; } .@{fa-css-prefix}-ambulance:before { content: @fa-var-ambulance; } .@{fa-css-prefix}-medkit:before { content: @fa-var-medkit; } .@{fa-css-prefix}-fighter-jet:before { content: @fa-var-fighter-jet; } .@{fa-css-prefix}-beer:before { content: @fa-var-beer; } .@{fa-css-prefix}-h-square:before { content: @fa-var-h-square; } .@{fa-css-prefix}-plus-square:before { content: @fa-var-plus-square; } .@{fa-css-prefix}-angle-double-left:before { content: @fa-var-angle-double-left; } .@{fa-css-prefix}-angle-double-right:before { content: @fa-var-angle-double-right; } .@{fa-css-prefix}-angle-double-up:before { content: @fa-var-angle-double-up; } .@{fa-css-prefix}-angle-double-down:before { content: @fa-var-angle-double-down; } .@{fa-css-prefix}-angle-left:before { content: @fa-var-angle-left; } .@{fa-css-prefix}-angle-right:before { content: @fa-var-angle-right; } .@{fa-css-prefix}-angle-up:before { content: @fa-var-angle-up; } .@{fa-css-prefix}-angle-down:before { content: @fa-var-angle-down; } .@{fa-css-prefix}-desktop:before { content: @fa-var-desktop; } .@{fa-css-prefix}-laptop:before { content: @fa-var-laptop; } .@{fa-css-prefix}-tablet:before { content: @fa-var-tablet; } .@{fa-css-prefix}-mobile-phone:before, .@{fa-css-prefix}-mobile:before { content: @fa-var-mobile; } .@{fa-css-prefix}-circle-o:before { content: @fa-var-circle-o; } .@{fa-css-prefix}-quote-left:before { content: @fa-var-quote-left; } .@{fa-css-prefix}-quote-right:before { content: @fa-var-quote-right; } .@{fa-css-prefix}-spinner:before { content: @fa-var-spinner; } .@{fa-css-prefix}-circle:before { content: @fa-var-circle; } .@{fa-css-prefix}-mail-reply:before, .@{fa-css-prefix}-reply:before { content: @fa-var-reply; } .@{fa-css-prefix}-github-alt:before { content: @fa-var-github-alt; } .@{fa-css-prefix}-folder-o:before { content: @fa-var-folder-o; } .@{fa-css-prefix}-folder-open-o:before { content: @fa-var-folder-open-o; } .@{fa-css-prefix}-smile-o:before { content: @fa-var-smile-o; } .@{fa-css-prefix}-frown-o:before { content: @fa-var-frown-o; } .@{fa-css-prefix}-meh-o:before { content: @fa-var-meh-o; } .@{fa-css-prefix}-gamepad:before { content: @fa-var-gamepad; } .@{fa-css-prefix}-keyboard-o:before { content: @fa-var-keyboard-o; } .@{fa-css-prefix}-flag-o:before { content: @fa-var-flag-o; } .@{fa-css-prefix}-flag-checkered:before { content: @fa-var-flag-checkered; } .@{fa-css-prefix}-terminal:before { content: @fa-var-terminal; } .@{fa-css-prefix}-code:before { content: @fa-var-code; } .@{fa-css-prefix}-mail-reply-all:before, .@{fa-css-prefix}-reply-all:before { content: @fa-var-reply-all; } .@{fa-css-prefix}-star-half-empty:before, .@{fa-css-prefix}-star-half-full:before, .@{fa-css-prefix}-star-half-o:before { content: @fa-var-star-half-o; } .@{fa-css-prefix}-location-arrow:before { content: @fa-var-location-arrow; } .@{fa-css-prefix}-crop:before { content: @fa-var-crop; } .@{fa-css-prefix}-code-fork:before { content: @fa-var-code-fork; } .@{fa-css-prefix}-unlink:before, .@{fa-css-prefix}-chain-broken:before { content: @fa-var-chain-broken; } .@{fa-css-prefix}-question:before { content: @fa-var-question; } .@{fa-css-prefix}-info:before { content: @fa-var-info; } .@{fa-css-prefix}-exclamation:before { content: @fa-var-exclamation; } .@{fa-css-prefix}-superscript:before { content: @fa-var-superscript; } .@{fa-css-prefix}-subscript:before { content: @fa-var-subscript; } .@{fa-css-prefix}-eraser:before { content: @fa-var-eraser; } .@{fa-css-prefix}-puzzle-piece:before { content: @fa-var-puzzle-piece; } .@{fa-css-prefix}-microphone:before { content: @fa-var-microphone; } .@{fa-css-prefix}-microphone-slash:before { content: @fa-var-microphone-slash; } .@{fa-css-prefix}-shield:before { content: @fa-var-shield; } .@{fa-css-prefix}-calendar-o:before { content: @fa-var-calendar-o; } .@{fa-css-prefix}-fire-extinguisher:before { content: @fa-var-fire-extinguisher; } .@{fa-css-prefix}-rocket:before { content: @fa-var-rocket; } .@{fa-css-prefix}-maxcdn:before { content: @fa-var-maxcdn; } .@{fa-css-prefix}-chevron-circle-left:before { content: @fa-var-chevron-circle-left; } .@{fa-css-prefix}-chevron-circle-right:before { content: @fa-var-chevron-circle-right; } .@{fa-css-prefix}-chevron-circle-up:before { content: @fa-var-chevron-circle-up; } .@{fa-css-prefix}-chevron-circle-down:before { content: @fa-var-chevron-circle-down; } .@{fa-css-prefix}-html5:before { content: @fa-var-html5; } .@{fa-css-prefix}-css3:before { content: @fa-var-css3; } .@{fa-css-prefix}-anchor:before { content: @fa-var-anchor; } .@{fa-css-prefix}-unlock-alt:before { content: @fa-var-unlock-alt; } .@{fa-css-prefix}-bullseye:before { content: @fa-var-bullseye; } .@{fa-css-prefix}-ellipsis-h:before { content: @fa-var-ellipsis-h; } .@{fa-css-prefix}-ellipsis-v:before { content: @fa-var-ellipsis-v; } .@{fa-css-prefix}-rss-square:before { content: @fa-var-rss-square; } .@{fa-css-prefix}-play-circle:before { content: @fa-var-play-circle; } .@{fa-css-prefix}-ticket:before { content: @fa-var-ticket; } .@{fa-css-prefix}-minus-square:before { content: @fa-var-minus-square; } .@{fa-css-prefix}-minus-square-o:before { content: @fa-var-minus-square-o; } .@{fa-css-prefix}-level-up:before { content: @fa-var-level-up; } .@{fa-css-prefix}-level-down:before { content: @fa-var-level-down; } .@{fa-css-prefix}-check-square:before { content: @fa-var-check-square; } .@{fa-css-prefix}-pencil-square:before { content: @fa-var-pencil-square; } .@{fa-css-prefix}-external-link-square:before { content: @fa-var-external-link-square; } .@{fa-css-prefix}-share-square:before { content: @fa-var-share-square; } .@{fa-css-prefix}-compass:before { content: @fa-var-compass; } .@{fa-css-prefix}-toggle-down:before, .@{fa-css-prefix}-caret-square-o-down:before { content: @fa-var-caret-square-o-down; } .@{fa-css-prefix}-toggle-up:before, .@{fa-css-prefix}-caret-square-o-up:before { content: @fa-var-caret-square-o-up; } .@{fa-css-prefix}-toggle-right:before, .@{fa-css-prefix}-caret-square-o-right:before { content: @fa-var-caret-square-o-right; } .@{fa-css-prefix}-euro:before, .@{fa-css-prefix}-eur:before { content: @fa-var-eur; } .@{fa-css-prefix}-gbp:before { content: @fa-var-gbp; } .@{fa-css-prefix}-dollar:before, .@{fa-css-prefix}-usd:before { content: @fa-var-usd; } .@{fa-css-prefix}-rupee:before, .@{fa-css-prefix}-inr:before { content: @fa-var-inr; } .@{fa-css-prefix}-cny:before, .@{fa-css-prefix}-rmb:before, .@{fa-css-prefix}-yen:before, .@{fa-css-prefix}-jpy:before { content: @fa-var-jpy; } .@{fa-css-prefix}-ruble:before, .@{fa-css-prefix}-rouble:before, .@{fa-css-prefix}-rub:before { content: @fa-var-rub; } .@{fa-css-prefix}-won:before, .@{fa-css-prefix}-krw:before { content: @fa-var-krw; } .@{fa-css-prefix}-bitcoin:before, .@{fa-css-prefix}-btc:before { content: @fa-var-btc; } .@{fa-css-prefix}-file:before { content: @fa-var-file; } .@{fa-css-prefix}-file-text:before { content: @fa-var-file-text; } .@{fa-css-prefix}-sort-alpha-asc:before { content: @fa-var-sort-alpha-asc; } .@{fa-css-prefix}-sort-alpha-desc:before { content: @fa-var-sort-alpha-desc; } .@{fa-css-prefix}-sort-amount-asc:before { content: @fa-var-sort-amount-asc; } .@{fa-css-prefix}-sort-amount-desc:before { content: @fa-var-sort-amount-desc; } .@{fa-css-prefix}-sort-numeric-asc:before { content: @fa-var-sort-numeric-asc; } .@{fa-css-prefix}-sort-numeric-desc:before { content: @fa-var-sort-numeric-desc; } .@{fa-css-prefix}-thumbs-up:before { content: @fa-var-thumbs-up; } .@{fa-css-prefix}-thumbs-down:before { content: @fa-var-thumbs-down; } .@{fa-css-prefix}-youtube-square:before { content: @fa-var-youtube-square; } .@{fa-css-prefix}-youtube:before { content: @fa-var-youtube; } .@{fa-css-prefix}-xing:before { content: @fa-var-xing; } .@{fa-css-prefix}-xing-square:before { content: @fa-var-xing-square; } .@{fa-css-prefix}-youtube-play:before { content: @fa-var-youtube-play; } .@{fa-css-prefix}-dropbox:before { content: @fa-var-dropbox; } .@{fa-css-prefix}-stack-overflow:before { content: @fa-var-stack-overflow; } .@{fa-css-prefix}-instagram:before { content: @fa-var-instagram; } .@{fa-css-prefix}-flickr:before { content: @fa-var-flickr; } .@{fa-css-prefix}-adn:before { content: @fa-var-adn; } .@{fa-css-prefix}-bitbucket:before { content: @fa-var-bitbucket; } .@{fa-css-prefix}-bitbucket-square:before { content: @fa-var-bitbucket-square; } .@{fa-css-prefix}-tumblr:before { content: @fa-var-tumblr; } .@{fa-css-prefix}-tumblr-square:before { content: @fa-var-tumblr-square; } .@{fa-css-prefix}-long-arrow-down:before { content: @fa-var-long-arrow-down; } .@{fa-css-prefix}-long-arrow-up:before { content: @fa-var-long-arrow-up; } .@{fa-css-prefix}-long-arrow-left:before { content: @fa-var-long-arrow-left; } .@{fa-css-prefix}-long-arrow-right:before { content: @fa-var-long-arrow-right; } .@{fa-css-prefix}-apple:before { content: @fa-var-apple; } .@{fa-css-prefix}-windows:before { content: @fa-var-windows; } .@{fa-css-prefix}-android:before { content: @fa-var-android; } .@{fa-css-prefix}-linux:before { content: @fa-var-linux; } .@{fa-css-prefix}-dribbble:before { content: @fa-var-dribbble; } .@{fa-css-prefix}-skype:before { content: @fa-var-skype; } .@{fa-css-prefix}-foursquare:before { content: @fa-var-foursquare; } .@{fa-css-prefix}-trello:before { content: @fa-var-trello; } .@{fa-css-prefix}-female:before { content: @fa-var-female; } .@{fa-css-prefix}-male:before { content: @fa-var-male; } .@{fa-css-prefix}-gittip:before { content: @fa-var-gittip; } .@{fa-css-prefix}-sun-o:before { content: @fa-var-sun-o; } .@{fa-css-prefix}-moon-o:before { content: @fa-var-moon-o; } .@{fa-css-prefix}-archive:before { content: @fa-var-archive; } .@{fa-css-prefix}-bug:before { content: @fa-var-bug; } .@{fa-css-prefix}-vk:before { content: @fa-var-vk; } .@{fa-css-prefix}-weibo:before { content: @fa-var-weibo; } .@{fa-css-prefix}-renren:before { content: @fa-var-renren; } .@{fa-css-prefix}-pagelines:before { content: @fa-var-pagelines; } .@{fa-css-prefix}-stack-exchange:before { content: @fa-var-stack-exchange; } .@{fa-css-prefix}-arrow-circle-o-right:before { content: @fa-var-arrow-circle-o-right; } .@{fa-css-prefix}-arrow-circle-o-left:before { content: @fa-var-arrow-circle-o-left; } .@{fa-css-prefix}-toggle-left:before, .@{fa-css-prefix}-caret-square-o-left:before { content: @fa-var-caret-square-o-left; } .@{fa-css-prefix}-dot-circle-o:before { content: @fa-var-dot-circle-o; } .@{fa-css-prefix}-wheelchair:before { content: @fa-var-wheelchair; } .@{fa-css-prefix}-vimeo-square:before { content: @fa-var-vimeo-square; } .@{fa-css-prefix}-turkish-lira:before, .@{fa-css-prefix}-try:before { content: @fa-var-try; } .@{fa-css-prefix}-plus-square-o:before { content: @fa-var-plus-square-o; } .@{fa-css-prefix}-space-shuttle:before { content: @fa-var-space-shuttle; } .@{fa-css-prefix}-slack:before { content: @fa-var-slack; } .@{fa-css-prefix}-envelope-square:before { content: @fa-var-envelope-square; } .@{fa-css-prefix}-wordpress:before { content: @fa-var-wordpress; } .@{fa-css-prefix}-openid:before { content: @fa-var-openid; } .@{fa-css-prefix}-institution:before, .@{fa-css-prefix}-bank:before, .@{fa-css-prefix}-university:before { content: @fa-var-university; } .@{fa-css-prefix}-mortar-board:before, .@{fa-css-prefix}-graduation-cap:before { content: @fa-var-graduation-cap; } .@{fa-css-prefix}-yahoo:before { content: @fa-var-yahoo; } .@{fa-css-prefix}-google:before { content: @fa-var-google; } .@{fa-css-prefix}-reddit:before { content: @fa-var-reddit; } .@{fa-css-prefix}-reddit-square:before { content: @fa-var-reddit-square; } .@{fa-css-prefix}-stumbleupon-circle:before { content: @fa-var-stumbleupon-circle; } .@{fa-css-prefix}-stumbleupon:before { content: @fa-var-stumbleupon; } .@{fa-css-prefix}-delicious:before { content: @fa-var-delicious; } .@{fa-css-prefix}-digg:before { content: @fa-var-digg; } .@{fa-css-prefix}-pied-piper-square:before, .@{fa-css-prefix}-pied-piper:before { content: @fa-var-pied-piper; } .@{fa-css-prefix}-pied-piper-alt:before { content: @fa-var-pied-piper-alt; } .@{fa-css-prefix}-drupal:before { content: @fa-var-drupal; } .@{fa-css-prefix}-joomla:before { content: @fa-var-joomla; } .@{fa-css-prefix}-language:before { content: @fa-var-language; } .@{fa-css-prefix}-fax:before { content: @fa-var-fax; } .@{fa-css-prefix}-building:before { content: @fa-var-building; } .@{fa-css-prefix}-child:before { content: @fa-var-child; } .@{fa-css-prefix}-paw:before { content: @fa-var-paw; } .@{fa-css-prefix}-spoon:before { content: @fa-var-spoon; } .@{fa-css-prefix}-cube:before { content: @fa-var-cube; } .@{fa-css-prefix}-cubes:before { content: @fa-var-cubes; } .@{fa-css-prefix}-behance:before { content: @fa-var-behance; } .@{fa-css-prefix}-behance-square:before { content: @fa-var-behance-square; } .@{fa-css-prefix}-steam:before { content: @fa-var-steam; } .@{fa-css-prefix}-steam-square:before { content: @fa-var-steam-square; } .@{fa-css-prefix}-recycle:before { content: @fa-var-recycle; } .@{fa-css-prefix}-automobile:before, .@{fa-css-prefix}-car:before { content: @fa-var-car; } .@{fa-css-prefix}-cab:before, .@{fa-css-prefix}-taxi:before { content: @fa-var-taxi; } .@{fa-css-prefix}-tree:before { content: @fa-var-tree; } .@{fa-css-prefix}-spotify:before { content: @fa-var-spotify; } .@{fa-css-prefix}-deviantart:before { content: @fa-var-deviantart; } .@{fa-css-prefix}-soundcloud:before { content: @fa-var-soundcloud; } .@{fa-css-prefix}-database:before { content: @fa-var-database; } .@{fa-css-prefix}-file-pdf-o:before { content: @fa-var-file-pdf-o; } .@{fa-css-prefix}-file-word-o:before { content: @fa-var-file-word-o; } .@{fa-css-prefix}-file-excel-o:before { content: @fa-var-file-excel-o; } .@{fa-css-prefix}-file-powerpoint-o:before { content: @fa-var-file-powerpoint-o; } .@{fa-css-prefix}-file-photo-o:before, .@{fa-css-prefix}-file-picture-o:before, .@{fa-css-prefix}-file-image-o:before { content: @fa-var-file-image-o; } .@{fa-css-prefix}-file-zip-o:before, .@{fa-css-prefix}-file-archive-o:before { content: @fa-var-file-archive-o; } .@{fa-css-prefix}-file-sound-o:before, .@{fa-css-prefix}-file-audio-o:before { content: @fa-var-file-audio-o; } .@{fa-css-prefix}-file-movie-o:before, .@{fa-css-prefix}-file-video-o:before { content: @fa-var-file-video-o; } .@{fa-css-prefix}-file-code-o:before { content: @fa-var-file-code-o; } .@{fa-css-prefix}-vine:before { content: @fa-var-vine; } .@{fa-css-prefix}-codepen:before { content: @fa-var-codepen; } .@{fa-css-prefix}-jsfiddle:before { content: @fa-var-jsfiddle; } .@{fa-css-prefix}-life-bouy:before, .@{fa-css-prefix}-life-saver:before, .@{fa-css-prefix}-support:before, .@{fa-css-prefix}-life-ring:before { content: @fa-var-life-ring; } .@{fa-css-prefix}-circle-o-notch:before { content: @fa-var-circle-o-notch; } .@{fa-css-prefix}-ra:before, .@{fa-css-prefix}-rebel:before { content: @fa-var-rebel; } .@{fa-css-prefix}-ge:before, .@{fa-css-prefix}-empire:before { content: @fa-var-empire; } .@{fa-css-prefix}-git-square:before { content: @fa-var-git-square; } .@{fa-css-prefix}-git:before { content: @fa-var-git; } .@{fa-css-prefix}-hacker-news:before { content: @fa-var-hacker-news; } .@{fa-css-prefix}-tencent-weibo:before { content: @fa-var-tencent-weibo; } .@{fa-css-prefix}-qq:before { content: @fa-var-qq; } .@{fa-css-prefix}-wechat:before, .@{fa-css-prefix}-weixin:before { content: @fa-var-weixin; } .@{fa-css-prefix}-send:before, .@{fa-css-prefix}-paper-plane:before { content: @fa-var-paper-plane; } .@{fa-css-prefix}-send-o:before, .@{fa-css-prefix}-paper-plane-o:before { content: @fa-var-paper-plane-o; } .@{fa-css-prefix}-history:before { content: @fa-var-history; } .@{fa-css-prefix}-circle-thin:before { content: @fa-var-circle-thin; } .@{fa-css-prefix}-header:before { content: @fa-var-header; } .@{fa-css-prefix}-paragraph:before { content: @fa-var-paragraph; } .@{fa-css-prefix}-sliders:before { content: @fa-var-sliders; } .@{fa-css-prefix}-share-alt:before { content: @fa-var-share-alt; } .@{fa-css-prefix}-share-alt-square:before { content: @fa-var-share-alt-square; } .@{fa-css-prefix}-bomb:before { content: @fa-var-bomb; } ================================================ FILE: assets/less/font-awesome/larger.less ================================================ // Icon Sizes // ------------------------- /* makes the font 33% larger relative to the icon container */ .@{fa-css-prefix}-lg { font-size: (4em / 3); line-height: (3em / 4); vertical-align: -15%; } .@{fa-css-prefix}-2x { font-size: 2em; } .@{fa-css-prefix}-3x { font-size: 3em; } .@{fa-css-prefix}-4x { font-size: 4em; } .@{fa-css-prefix}-5x { font-size: 5em; } ================================================ FILE: assets/less/font-awesome/list.less ================================================ // List Icons // ------------------------- .@{fa-css-prefix}-ul { padding-left: 0; margin-left: @fa-li-width; list-style-type: none; > li { position: relative; } } .@{fa-css-prefix}-li { position: absolute; left: -@fa-li-width; width: @fa-li-width; top: (2em / 14); text-align: center; &.@{fa-css-prefix}-lg { left: -@fa-li-width + (4em / 14); } } ================================================ FILE: assets/less/font-awesome/mixins.less ================================================ // Mixins // -------------------------- .fa-icon-rotate(@degrees, @rotation) { filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation); -webkit-transform: rotate(@degrees); -moz-transform: rotate(@degrees); -ms-transform: rotate(@degrees); -o-transform: rotate(@degrees); transform: rotate(@degrees); } .fa-icon-flip(@horiz, @vert, @rotation) { filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation, mirror=1); -webkit-transform: scale(@horiz, @vert); -moz-transform: scale(@horiz, @vert); -ms-transform: scale(@horiz, @vert); -o-transform: scale(@horiz, @vert); transform: scale(@horiz, @vert); } ================================================ FILE: assets/less/font-awesome/path.less ================================================ /* FONT PATH * -------------------------- */ @font-face { font-family: 'FontAwesome'; src: ~"url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}')"; src: ~"url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype')", ~"url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff')", ~"url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype')", ~"url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg')"; // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts font-weight: normal; font-style: normal; } ================================================ FILE: assets/less/font-awesome/rotated-flipped.less ================================================ // Rotated & Flipped Icons // ------------------------- .@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); } .@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); } .@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); } .@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); } .@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); } ================================================ FILE: assets/less/font-awesome/spinning.less ================================================ // Spinning Icons // -------------------------- .@{fa-css-prefix}-spin { -webkit-animation: spin 2s infinite linear; -moz-animation: spin 2s infinite linear; -o-animation: spin 2s infinite linear; animation: spin 2s infinite linear; } @-moz-keyframes spin { 0% { -moz-transform: rotate(0deg); } 100% { -moz-transform: rotate(359deg); } } @-webkit-keyframes spin { 0% { -webkit-transform: rotate(0deg); } 100% { -webkit-transform: rotate(359deg); } } @-o-keyframes spin { 0% { -o-transform: rotate(0deg); } 100% { -o-transform: rotate(359deg); } } @keyframes spin { 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { -webkit-transform: rotate(359deg); transform: rotate(359deg); } } ================================================ FILE: assets/less/font-awesome/stacked.less ================================================ // Stacked Icons // ------------------------- .@{fa-css-prefix}-stack { position: relative; display: inline-block; width: 2em; height: 2em; line-height: 2em; vertical-align: middle; } .@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x { position: absolute; left: 0; width: 100%; text-align: center; } .@{fa-css-prefix}-stack-1x { line-height: inherit; } .@{fa-css-prefix}-stack-2x { font-size: 2em; } .@{fa-css-prefix}-inverse { color: @fa-inverse; } ================================================ FILE: assets/less/font-awesome/variables.less ================================================ // Variables // -------------------------- @fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.1.0/fonts"; // for referencing Bootstrap CDN font files directly @fa-css-prefix: fa; @fa-version: "4.1.0"; @fa-border-color: #eee; @fa-inverse: #fff; @fa-li-width: (30em / 14); @fa-var-adjust: "\f042"; @fa-var-adn: "\f170"; @fa-var-align-center: "\f037"; @fa-var-align-justify: "\f039"; @fa-var-align-left: "\f036"; @fa-var-align-right: "\f038"; @fa-var-ambulance: "\f0f9"; @fa-var-anchor: "\f13d"; @fa-var-android: "\f17b"; @fa-var-angle-double-down: "\f103"; @fa-var-angle-double-left: "\f100"; @fa-var-angle-double-right: "\f101"; @fa-var-angle-double-up: "\f102"; @fa-var-angle-down: "\f107"; @fa-var-angle-left: "\f104"; @fa-var-angle-right: "\f105"; @fa-var-angle-up: "\f106"; @fa-var-apple: "\f179"; @fa-var-archive: "\f187"; @fa-var-arrow-circle-down: "\f0ab"; @fa-var-arrow-circle-left: "\f0a8"; @fa-var-arrow-circle-o-down: "\f01a"; @fa-var-arrow-circle-o-left: "\f190"; @fa-var-arrow-circle-o-right: "\f18e"; @fa-var-arrow-circle-o-up: "\f01b"; @fa-var-arrow-circle-right: "\f0a9"; @fa-var-arrow-circle-up: "\f0aa"; @fa-var-arrow-down: "\f063"; @fa-var-arrow-left: "\f060"; @fa-var-arrow-right: "\f061"; @fa-var-arrow-up: "\f062"; @fa-var-arrows: "\f047"; @fa-var-arrows-alt: "\f0b2"; @fa-var-arrows-h: "\f07e"; @fa-var-arrows-v: "\f07d"; @fa-var-asterisk: "\f069"; @fa-var-automobile: "\f1b9"; @fa-var-backward: "\f04a"; @fa-var-ban: "\f05e"; @fa-var-bank: "\f19c"; @fa-var-bar-chart-o: "\f080"; @fa-var-barcode: "\f02a"; @fa-var-bars: "\f0c9"; @fa-var-beer: "\f0fc"; @fa-var-behance: "\f1b4"; @fa-var-behance-square: "\f1b5"; @fa-var-bell: "\f0f3"; @fa-var-bell-o: "\f0a2"; @fa-var-bitbucket: "\f171"; @fa-var-bitbucket-square: "\f172"; @fa-var-bitcoin: "\f15a"; @fa-var-bold: "\f032"; @fa-var-bolt: "\f0e7"; @fa-var-bomb: "\f1e2"; @fa-var-book: "\f02d"; @fa-var-bookmark: "\f02e"; @fa-var-bookmark-o: "\f097"; @fa-var-briefcase: "\f0b1"; @fa-var-btc: "\f15a"; @fa-var-bug: "\f188"; @fa-var-building: "\f1ad"; @fa-var-building-o: "\f0f7"; @fa-var-bullhorn: "\f0a1"; @fa-var-bullseye: "\f140"; @fa-var-cab: "\f1ba"; @fa-var-calendar: "\f073"; @fa-var-calendar-o: "\f133"; @fa-var-camera: "\f030"; @fa-var-camera-retro: "\f083"; @fa-var-car: "\f1b9"; @fa-var-caret-down: "\f0d7"; @fa-var-caret-left: "\f0d9"; @fa-var-caret-right: "\f0da"; @fa-var-caret-square-o-down: "\f150"; @fa-var-caret-square-o-left: "\f191"; @fa-var-caret-square-o-right: "\f152"; @fa-var-caret-square-o-up: "\f151"; @fa-var-caret-up: "\f0d8"; @fa-var-certificate: "\f0a3"; @fa-var-chain: "\f0c1"; @fa-var-chain-broken: "\f127"; @fa-var-check: "\f00c"; @fa-var-check-circle: "\f058"; @fa-var-check-circle-o: "\f05d"; @fa-var-check-square: "\f14a"; @fa-var-check-square-o: "\f046"; @fa-var-chevron-circle-down: "\f13a"; @fa-var-chevron-circle-left: "\f137"; @fa-var-chevron-circle-right: "\f138"; @fa-var-chevron-circle-up: "\f139"; @fa-var-chevron-down: "\f078"; @fa-var-chevron-left: "\f053"; @fa-var-chevron-right: "\f054"; @fa-var-chevron-up: "\f077"; @fa-var-child: "\f1ae"; @fa-var-circle: "\f111"; @fa-var-circle-o: "\f10c"; @fa-var-circle-o-notch: "\f1ce"; @fa-var-circle-thin: "\f1db"; @fa-var-clipboard: "\f0ea"; @fa-var-clock-o: "\f017"; @fa-var-cloud: "\f0c2"; @fa-var-cloud-download: "\f0ed"; @fa-var-cloud-upload: "\f0ee"; @fa-var-cny: "\f157"; @fa-var-code: "\f121"; @fa-var-code-fork: "\f126"; @fa-var-codepen: "\f1cb"; @fa-var-coffee: "\f0f4"; @fa-var-cog: "\f013"; @fa-var-cogs: "\f085"; @fa-var-columns: "\f0db"; @fa-var-comment: "\f075"; @fa-var-comment-o: "\f0e5"; @fa-var-comments: "\f086"; @fa-var-comments-o: "\f0e6"; @fa-var-compass: "\f14e"; @fa-var-compress: "\f066"; @fa-var-copy: "\f0c5"; @fa-var-credit-card: "\f09d"; @fa-var-crop: "\f125"; @fa-var-crosshairs: "\f05b"; @fa-var-css3: "\f13c"; @fa-var-cube: "\f1b2"; @fa-var-cubes: "\f1b3"; @fa-var-cut: "\f0c4"; @fa-var-cutlery: "\f0f5"; @fa-var-dashboard: "\f0e4"; @fa-var-database: "\f1c0"; @fa-var-dedent: "\f03b"; @fa-var-delicious: "\f1a5"; @fa-var-desktop: "\f108"; @fa-var-deviantart: "\f1bd"; @fa-var-digg: "\f1a6"; @fa-var-dollar: "\f155"; @fa-var-dot-circle-o: "\f192"; @fa-var-download: "\f019"; @fa-var-dribbble: "\f17d"; @fa-var-dropbox: "\f16b"; @fa-var-drupal: "\f1a9"; @fa-var-edit: "\f044"; @fa-var-eject: "\f052"; @fa-var-ellipsis-h: "\f141"; @fa-var-ellipsis-v: "\f142"; @fa-var-empire: "\f1d1"; @fa-var-envelope: "\f0e0"; @fa-var-envelope-o: "\f003"; @fa-var-envelope-square: "\f199"; @fa-var-eraser: "\f12d"; @fa-var-eur: "\f153"; @fa-var-euro: "\f153"; @fa-var-exchange: "\f0ec"; @fa-var-exclamation: "\f12a"; @fa-var-exclamation-circle: "\f06a"; @fa-var-exclamation-triangle: "\f071"; @fa-var-expand: "\f065"; @fa-var-external-link: "\f08e"; @fa-var-external-link-square: "\f14c"; @fa-var-eye: "\f06e"; @fa-var-eye-slash: "\f070"; @fa-var-facebook: "\f09a"; @fa-var-facebook-square: "\f082"; @fa-var-fast-backward: "\f049"; @fa-var-fast-forward: "\f050"; @fa-var-fax: "\f1ac"; @fa-var-female: "\f182"; @fa-var-fighter-jet: "\f0fb"; @fa-var-file: "\f15b"; @fa-var-file-archive-o: "\f1c6"; @fa-var-file-audio-o: "\f1c7"; @fa-var-file-code-o: "\f1c9"; @fa-var-file-excel-o: "\f1c3"; @fa-var-file-image-o: "\f1c5"; @fa-var-file-movie-o: "\f1c8"; @fa-var-file-o: "\f016"; @fa-var-file-pdf-o: "\f1c1"; @fa-var-file-photo-o: "\f1c5"; @fa-var-file-picture-o: "\f1c5"; @fa-var-file-powerpoint-o: "\f1c4"; @fa-var-file-sound-o: "\f1c7"; @fa-var-file-text: "\f15c"; @fa-var-file-text-o: "\f0f6"; @fa-var-file-video-o: "\f1c8"; @fa-var-file-word-o: "\f1c2"; @fa-var-file-zip-o: "\f1c6"; @fa-var-files-o: "\f0c5"; @fa-var-film: "\f008"; @fa-var-filter: "\f0b0"; @fa-var-fire: "\f06d"; @fa-var-fire-extinguisher: "\f134"; @fa-var-flag: "\f024"; @fa-var-flag-checkered: "\f11e"; @fa-var-flag-o: "\f11d"; @fa-var-flash: "\f0e7"; @fa-var-flask: "\f0c3"; @fa-var-flickr: "\f16e"; @fa-var-floppy-o: "\f0c7"; @fa-var-folder: "\f07b"; @fa-var-folder-o: "\f114"; @fa-var-folder-open: "\f07c"; @fa-var-folder-open-o: "\f115"; @fa-var-font: "\f031"; @fa-var-forward: "\f04e"; @fa-var-foursquare: "\f180"; @fa-var-frown-o: "\f119"; @fa-var-gamepad: "\f11b"; @fa-var-gavel: "\f0e3"; @fa-var-gbp: "\f154"; @fa-var-ge: "\f1d1"; @fa-var-gear: "\f013"; @fa-var-gears: "\f085"; @fa-var-gift: "\f06b"; @fa-var-git: "\f1d3"; @fa-var-git-square: "\f1d2"; @fa-var-github: "\f09b"; @fa-var-github-alt: "\f113"; @fa-var-github-square: "\f092"; @fa-var-gittip: "\f184"; @fa-var-glass: "\f000"; @fa-var-globe: "\f0ac"; @fa-var-google: "\f1a0"; @fa-var-google-plus: "\f0d5"; @fa-var-google-plus-square: "\f0d4"; @fa-var-graduation-cap: "\f19d"; @fa-var-group: "\f0c0"; @fa-var-h-square: "\f0fd"; @fa-var-hacker-news: "\f1d4"; @fa-var-hand-o-down: "\f0a7"; @fa-var-hand-o-left: "\f0a5"; @fa-var-hand-o-right: "\f0a4"; @fa-var-hand-o-up: "\f0a6"; @fa-var-hdd-o: "\f0a0"; @fa-var-header: "\f1dc"; @fa-var-headphones: "\f025"; @fa-var-heart: "\f004"; @fa-var-heart-o: "\f08a"; @fa-var-history: "\f1da"; @fa-var-home: "\f015"; @fa-var-hospital-o: "\f0f8"; @fa-var-html5: "\f13b"; @fa-var-image: "\f03e"; @fa-var-inbox: "\f01c"; @fa-var-indent: "\f03c"; @fa-var-info: "\f129"; @fa-var-info-circle: "\f05a"; @fa-var-inr: "\f156"; @fa-var-instagram: "\f16d"; @fa-var-institution: "\f19c"; @fa-var-italic: "\f033"; @fa-var-joomla: "\f1aa"; @fa-var-jpy: "\f157"; @fa-var-jsfiddle: "\f1cc"; @fa-var-key: "\f084"; @fa-var-keyboard-o: "\f11c"; @fa-var-krw: "\f159"; @fa-var-language: "\f1ab"; @fa-var-laptop: "\f109"; @fa-var-leaf: "\f06c"; @fa-var-legal: "\f0e3"; @fa-var-lemon-o: "\f094"; @fa-var-level-down: "\f149"; @fa-var-level-up: "\f148"; @fa-var-life-bouy: "\f1cd"; @fa-var-life-ring: "\f1cd"; @fa-var-life-saver: "\f1cd"; @fa-var-lightbulb-o: "\f0eb"; @fa-var-link: "\f0c1"; @fa-var-linkedin: "\f0e1"; @fa-var-linkedin-square: "\f08c"; @fa-var-linux: "\f17c"; @fa-var-list: "\f03a"; @fa-var-list-alt: "\f022"; @fa-var-list-ol: "\f0cb"; @fa-var-list-ul: "\f0ca"; @fa-var-location-arrow: "\f124"; @fa-var-lock: "\f023"; @fa-var-long-arrow-down: "\f175"; @fa-var-long-arrow-left: "\f177"; @fa-var-long-arrow-right: "\f178"; @fa-var-long-arrow-up: "\f176"; @fa-var-magic: "\f0d0"; @fa-var-magnet: "\f076"; @fa-var-mail-forward: "\f064"; @fa-var-mail-reply: "\f112"; @fa-var-mail-reply-all: "\f122"; @fa-var-male: "\f183"; @fa-var-map-marker: "\f041"; @fa-var-maxcdn: "\f136"; @fa-var-medkit: "\f0fa"; @fa-var-meh-o: "\f11a"; @fa-var-microphone: "\f130"; @fa-var-microphone-slash: "\f131"; @fa-var-minus: "\f068"; @fa-var-minus-circle: "\f056"; @fa-var-minus-square: "\f146"; @fa-var-minus-square-o: "\f147"; @fa-var-mobile: "\f10b"; @fa-var-mobile-phone: "\f10b"; @fa-var-money: "\f0d6"; @fa-var-moon-o: "\f186"; @fa-var-mortar-board: "\f19d"; @fa-var-music: "\f001"; @fa-var-navicon: "\f0c9"; @fa-var-openid: "\f19b"; @fa-var-outdent: "\f03b"; @fa-var-pagelines: "\f18c"; @fa-var-paper-plane: "\f1d8"; @fa-var-paper-plane-o: "\f1d9"; @fa-var-paperclip: "\f0c6"; @fa-var-paragraph: "\f1dd"; @fa-var-paste: "\f0ea"; @fa-var-pause: "\f04c"; @fa-var-paw: "\f1b0"; @fa-var-pencil: "\f040"; @fa-var-pencil-square: "\f14b"; @fa-var-pencil-square-o: "\f044"; @fa-var-phone: "\f095"; @fa-var-phone-square: "\f098"; @fa-var-photo: "\f03e"; @fa-var-picture-o: "\f03e"; @fa-var-pied-piper: "\f1a7"; @fa-var-pied-piper-alt: "\f1a8"; @fa-var-pied-piper-square: "\f1a7"; @fa-var-pinterest: "\f0d2"; @fa-var-pinterest-square: "\f0d3"; @fa-var-plane: "\f072"; @fa-var-play: "\f04b"; @fa-var-play-circle: "\f144"; @fa-var-play-circle-o: "\f01d"; @fa-var-plus: "\f067"; @fa-var-plus-circle: "\f055"; @fa-var-plus-square: "\f0fe"; @fa-var-plus-square-o: "\f196"; @fa-var-power-off: "\f011"; @fa-var-print: "\f02f"; @fa-var-puzzle-piece: "\f12e"; @fa-var-qq: "\f1d6"; @fa-var-qrcode: "\f029"; @fa-var-question: "\f128"; @fa-var-question-circle: "\f059"; @fa-var-quote-left: "\f10d"; @fa-var-quote-right: "\f10e"; @fa-var-ra: "\f1d0"; @fa-var-random: "\f074"; @fa-var-rebel: "\f1d0"; @fa-var-recycle: "\f1b8"; @fa-var-reddit: "\f1a1"; @fa-var-reddit-square: "\f1a2"; @fa-var-refresh: "\f021"; @fa-var-renren: "\f18b"; @fa-var-reorder: "\f0c9"; @fa-var-repeat: "\f01e"; @fa-var-reply: "\f112"; @fa-var-reply-all: "\f122"; @fa-var-retweet: "\f079"; @fa-var-rmb: "\f157"; @fa-var-road: "\f018"; @fa-var-rocket: "\f135"; @fa-var-rotate-left: "\f0e2"; @fa-var-rotate-right: "\f01e"; @fa-var-rouble: "\f158"; @fa-var-rss: "\f09e"; @fa-var-rss-square: "\f143"; @fa-var-rub: "\f158"; @fa-var-ruble: "\f158"; @fa-var-rupee: "\f156"; @fa-var-save: "\f0c7"; @fa-var-scissors: "\f0c4"; @fa-var-search: "\f002"; @fa-var-search-minus: "\f010"; @fa-var-search-plus: "\f00e"; @fa-var-send: "\f1d8"; @fa-var-send-o: "\f1d9"; @fa-var-share: "\f064"; @fa-var-share-alt: "\f1e0"; @fa-var-share-alt-square: "\f1e1"; @fa-var-share-square: "\f14d"; @fa-var-share-square-o: "\f045"; @fa-var-shield: "\f132"; @fa-var-shopping-cart: "\f07a"; @fa-var-sign-in: "\f090"; @fa-var-sign-out: "\f08b"; @fa-var-signal: "\f012"; @fa-var-sitemap: "\f0e8"; @fa-var-skype: "\f17e"; @fa-var-slack: "\f198"; @fa-var-sliders: "\f1de"; @fa-var-smile-o: "\f118"; @fa-var-sort: "\f0dc"; @fa-var-sort-alpha-asc: "\f15d"; @fa-var-sort-alpha-desc: "\f15e"; @fa-var-sort-amount-asc: "\f160"; @fa-var-sort-amount-desc: "\f161"; @fa-var-sort-asc: "\f0de"; @fa-var-sort-desc: "\f0dd"; @fa-var-sort-down: "\f0dd"; @fa-var-sort-numeric-asc: "\f162"; @fa-var-sort-numeric-desc: "\f163"; @fa-var-sort-up: "\f0de"; @fa-var-soundcloud: "\f1be"; @fa-var-space-shuttle: "\f197"; @fa-var-spinner: "\f110"; @fa-var-spoon: "\f1b1"; @fa-var-spotify: "\f1bc"; @fa-var-square: "\f0c8"; @fa-var-square-o: "\f096"; @fa-var-stack-exchange: "\f18d"; @fa-var-stack-overflow: "\f16c"; @fa-var-star: "\f005"; @fa-var-star-half: "\f089"; @fa-var-star-half-empty: "\f123"; @fa-var-star-half-full: "\f123"; @fa-var-star-half-o: "\f123"; @fa-var-star-o: "\f006"; @fa-var-steam: "\f1b6"; @fa-var-steam-square: "\f1b7"; @fa-var-step-backward: "\f048"; @fa-var-step-forward: "\f051"; @fa-var-stethoscope: "\f0f1"; @fa-var-stop: "\f04d"; @fa-var-strikethrough: "\f0cc"; @fa-var-stumbleupon: "\f1a4"; @fa-var-stumbleupon-circle: "\f1a3"; @fa-var-subscript: "\f12c"; @fa-var-suitcase: "\f0f2"; @fa-var-sun-o: "\f185"; @fa-var-superscript: "\f12b"; @fa-var-support: "\f1cd"; @fa-var-table: "\f0ce"; @fa-var-tablet: "\f10a"; @fa-var-tachometer: "\f0e4"; @fa-var-tag: "\f02b"; @fa-var-tags: "\f02c"; @fa-var-tasks: "\f0ae"; @fa-var-taxi: "\f1ba"; @fa-var-tencent-weibo: "\f1d5"; @fa-var-terminal: "\f120"; @fa-var-text-height: "\f034"; @fa-var-text-width: "\f035"; @fa-var-th: "\f00a"; @fa-var-th-large: "\f009"; @fa-var-th-list: "\f00b"; @fa-var-thumb-tack: "\f08d"; @fa-var-thumbs-down: "\f165"; @fa-var-thumbs-o-down: "\f088"; @fa-var-thumbs-o-up: "\f087"; @fa-var-thumbs-up: "\f164"; @fa-var-ticket: "\f145"; @fa-var-times: "\f00d"; @fa-var-times-circle: "\f057"; @fa-var-times-circle-o: "\f05c"; @fa-var-tint: "\f043"; @fa-var-toggle-down: "\f150"; @fa-var-toggle-left: "\f191"; @fa-var-toggle-right: "\f152"; @fa-var-toggle-up: "\f151"; @fa-var-trash-o: "\f014"; @fa-var-tree: "\f1bb"; @fa-var-trello: "\f181"; @fa-var-trophy: "\f091"; @fa-var-truck: "\f0d1"; @fa-var-try: "\f195"; @fa-var-tumblr: "\f173"; @fa-var-tumblr-square: "\f174"; @fa-var-turkish-lira: "\f195"; @fa-var-twitter: "\f099"; @fa-var-twitter-square: "\f081"; @fa-var-umbrella: "\f0e9"; @fa-var-underline: "\f0cd"; @fa-var-undo: "\f0e2"; @fa-var-university: "\f19c"; @fa-var-unlink: "\f127"; @fa-var-unlock: "\f09c"; @fa-var-unlock-alt: "\f13e"; @fa-var-unsorted: "\f0dc"; @fa-var-upload: "\f093"; @fa-var-usd: "\f155"; @fa-var-user: "\f007"; @fa-var-user-md: "\f0f0"; @fa-var-users: "\f0c0"; @fa-var-video-camera: "\f03d"; @fa-var-vimeo-square: "\f194"; @fa-var-vine: "\f1ca"; @fa-var-vk: "\f189"; @fa-var-volume-down: "\f027"; @fa-var-volume-off: "\f026"; @fa-var-volume-up: "\f028"; @fa-var-warning: "\f071"; @fa-var-wechat: "\f1d7"; @fa-var-weibo: "\f18a"; @fa-var-weixin: "\f1d7"; @fa-var-wheelchair: "\f193"; @fa-var-windows: "\f17a"; @fa-var-won: "\f159"; @fa-var-wordpress: "\f19a"; @fa-var-wrench: "\f0ad"; @fa-var-xing: "\f168"; @fa-var-xing-square: "\f169"; @fa-var-yahoo: "\f19e"; @fa-var-yen: "\f157"; @fa-var-youtube: "\f167"; @fa-var-youtube-play: "\f16a"; @fa-var-youtube-square: "\f166"; ================================================ FILE: assets/less/font-awesome.less ================================================ /*! * Font Awesome 4.1.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) */ @import "variables.less"; @import "mixins.less"; @import "path.less"; @import "core.less"; @import "larger.less"; @import "fixed-width.less"; @import "list.less"; @import "bordered-pulled.less"; @import "spinning.less"; @import "rotated-flipped.less"; @import "stacked.less"; @import "icons.less"; ================================================ FILE: assets/less/gist.less ================================================ .gist { .highlight { color:#000; } .render-container .render-viewer-error, .render-container .render-viewer-fatal, .render-container .octospinner { display:none; } .gist-render iframe { width:100%; } .gist-file.gist-render .highlight { border:none; } .gist-file .gist-meta .highlight a { font-weight:700; color:#666; text-decoration:none; } .highlight { background:#fff; } .highlight .err { color:#a61717; background-color:#e3d2d2; } .highlight .cp { color:#999; font-weight:700; } .highlight .cs { color:#999; font-weight:700; font-style:italic; } .highlight .gd { color:#000; background-color:#fdd; } .highlight .gd .x { color:#000; background-color:#faa; } .highlight .ge { color:#000; font-style:italic; } .highlight .gi { color:#000; background-color:#dfd; } .highlight .gi .x { color:#000; background-color:#afa; } .highlight .go { color:#888; } .highlight .gs { font-weight:700; } .highlight .gu { color:#aaa; } .highlight .nb { color:#0086b3; } .highlight .ni { color:purple; } .highlight .nt { color:navy; } .highlight .w { color:#bbb; } .highlight .sr { color:#009926; } .highlight .ss { color:#990073; } .highlight .c, .highlight .cm, .highlight .c1 { color:#998; font-style:italic; } .highlight .k, .highlight .o, .highlight .kc, .highlight .kd, .highlight .kp, .highlight .kr, .highlight .ow, .highlight .n, .highlight .p { color:#000; font-weight:700; } .highlight .gr, .highlight .gt { color:#a00; } .highlight .gh, .highlight .bp { color:#999; } .highlight .gp, .highlight .nn { color:#555; } .highlight .kt, .highlight .nc { color:#458; font-weight:700; } .highlight .m, .highlight .mf, .highlight .mh, .highlight .mi, .highlight .mo, .highlight .il { color:#099; } .highlight .s, .highlight .sb, .highlight .sc, .highlight .sd, .highlight .s2, .highlight .se, .highlight .sh, .highlight .si, .highlight .sx, .highlight .s1 { color:#d14; } .highlight .na, .highlight .no, .highlight .nv, .highlight .vc, .highlight .vg, .highlight .vi { color:teal; } .highlight .ne, .highlight .nf { color:#900; font-weight:700; } } ================================================ FILE: assets/less/grid.less ================================================ // Defining number of columns in the grid. // Common Values would be 12, 16 or 24 @width: 100%; @def_grid: 12; @margin: 0; .container(){ margin:0 auto; width:@width; } // Works out the width of elements based // on total number of columns and width // number of columns being displayed. // Removes 20px for margins .grid(@grid:@def_grid,@cols:'',@float:left,@display:inline){ display:@display; float:@float; width:(100%/@grid * @cols) - (@margin * 2); } // Allows for padding before element .prefix(@grid:@def_grid,@cols:''){ margin-left:(100%/@grid * @cols); } // Allows for padding after element .suffix(@grid:@def_grid,@cols:''){ margin-right:(100%/@grid * @cols); } // Removes left margin .first(){ margin-left:0; } // Removes right margin .last(){ margin-right:0; } .push(@grid:@def_grid,@move:'') { position:relative; left:(100%/@grid * @move); } .pull(@grid:@def_grid,@move:''){ position:relative; left:(100%/@grid * @move) * -1; } ================================================ FILE: assets/less/magnific-popup.less ================================================ /* Magnific Popup CSS */ // Modified by Michael Rose .mfp-bg { top: 0; left: 0; width: 100%; height: 100%; z-index: 502; overflow: hidden; position: fixed; background: #0b0b0b; opacity: 0.8; filter: alpha(opacity=80); } .mfp-wrap { top: 0; left: 0; width: 100%; height: 100%; z-index: 503; position: fixed; outline: none !important; -webkit-backface-visibility: hidden; } .mfp-container { height: 100%; text-align: center; position: absolute; width: 100%; height: 100%; left: 0; top: 0; padding: 0 8px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .mfp-container:before { content: ''; display: inline-block; height: 100%; vertical-align: middle; } .mfp-align-top .mfp-container:before { display: none; } .mfp-content { position: relative; display: inline-block; vertical-align: middle; margin: 0 auto; text-align: left; z-index: 505; } .mfp-inline-holder .mfp-content, .mfp-ajax-holder .mfp-content { width: 100%; cursor: auto; &:after { content: ''; display: block; width: auto; height: auto; position: absolute; left: 0; top: 0; bottom: 0; right: 0; z-index: -1; box-shadow: 0 0 8px rgba(0, 0, 0, 0.6); } } .mfp-ajax-cur { cursor: progress; } .mfp-zoom-out-cur, .mfp-zoom-out-cur .mfp-image-holder .mfp-close { cursor: -moz-zoom-out; cursor: -webkit-zoom-out; cursor: zoom-out; } .mfp-zoom, .image-popup { cursor: pointer; cursor: -webkit-zoom-in; cursor: -moz-zoom-in; cursor: zoom-in; } .mfp-auto-cursor .mfp-content { cursor: auto; } .mfp-close, .mfp-arrow, .mfp-preloader, .mfp-counter { -webkit-user-select: none; -moz-user-select: none; user-select: none; } .mfp-loading.mfp-figure { display: none; } .mfp-hide { display: none !important; } .mfp-preloader { color: #cccccc; position: absolute; top: 50%; width: auto; text-align: center; margin-top: -0.8em; left: 8px; right: 8px; z-index: 504; } .mfp-preloader a { color: #cccccc; } .mfp-preloader a:hover { color: white; } .mfp-s-ready .mfp-preloader { display: none; } .mfp-s-error .mfp-content { display: none; } button.mfp-close, button.mfp-arrow { overflow: visible; cursor: pointer; border: 0; -webkit-appearance: none; display: block; padding: 0; z-index: 506; } button.mfp-close { background: transparent; } button::-moz-focus-inner { padding: 0; border: 0; } .mfp-close { width: 44px; height: 44px; line-height: 44px; position: absolute; right: 0; top: 0; text-decoration: none; text-align: center; opacity: 0.65; padding: 0 0 18px 10px; color: white; font-style: normal; font-size: 28px; font-family: Arial, Baskerville, monospace; } .mfp-close:hover, .mfp-close:focus { opacity: 1; } .mfp-close:active { top: 1px; } .mfp-close-btn-in .mfp-close { color: #333333; } .mfp-image-holder .mfp-close, .mfp-iframe-holder .mfp-close { color: white; right: -6px; text-align: right; padding-right: 6px; width: 100%; } .mfp-counter { position: absolute; top: 0; right: 0; color: #cccccc; font-size: 12px; line-height: 18px; } .mfp-arrow { .hide-text(); position: absolute; top: 0; opacity: 0.65; margin: 0; top: 50%; margin-top: -55px; padding: 0; width: 90px; height: 110px; background-color: @black; .transition(opacity 0.2s ease-in-out); -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } .mfp-arrow:active { margin-top: -54px; } .mfp-arrow:hover, .mfp-arrow:focus { opacity: 1; } .mfp-arrow:before, .mfp-arrow:after, .mfp-arrow .mfp-b, .mfp-arrow .mfp-a { content: ''; display: block; width: 0; height: 0; position: absolute; left: 0; top: 0; margin-top: 35px; margin-left: 35px; border: solid transparent; } .mfp-arrow:after, .mfp-arrow .mfp-a { opacity: 0.8; border-top-width: 12px; border-bottom-width: 12px; top: 8px; } .mfp-arrow:before, .mfp-arrow .mfp-b { border-top-width: 20px; border-bottom-width: 20px; } .mfp-arrow-left { left: 0; .border-radius(5px, 5px, 0, 0); } .mfp-arrow-left:after, .mfp-arrow-left .mfp-a { border-right: 12px solid #fff; left: 5px; } .mfp-arrow-left:before, .mfp-arrow-left .mfp-b { border-right: 20px solid #fff; } .mfp-arrow-right { right: 0; .border-radius(0, 0, 5px, 5px); } .mfp-arrow-right:after, .mfp-arrow-right .mfp-a { border-left: 12px solid #fff; left: 3px; } .mfp-arrow-right:before, .mfp-arrow-right .mfp-b { border-left: 20px solid #fff; } .mfp-iframe-holder { padding-top: 40px; padding-bottom: 40px; } .mfp-iframe-holder .mfp-content { line-height: 0; width: 100%; max-width: 900px; } .mfp-iframe-scaler { width: 100%; height: 0; overflow: hidden; padding-top: 56.25%; } .mfp-iframe-scaler iframe { position: absolute; display: block; top: 0; left: 0; width: 100%; height: 100%; box-shadow: 0 0 8px rgba(0, 0, 0, 0.6); background: black; } .mfp-iframe-holder .mfp-close { top: -40px; } /* Main image in popup */ img.mfp-img { width: auto; max-width: 100%; height: auto; display: block; line-height: 0; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding: 40px 0 40px; margin: 0 auto; } /* The shadow behind the image */ .mfp-figure:after { content: ''; position: absolute; left: 0; top: 40px; bottom: 40px; display: block; right: 0; width: auto; height: auto; z-index: -1; box-shadow: 0 0 8px rgba(0, 0, 0, 0.6); } .mfp-figure { line-height: 0; } .mfp-bottom-bar { margin-top: -36px; position: absolute; top: 100%; left: 0; width: 100%; cursor: auto; } .mfp-title { text-align: left; line-height: 18px; color: #f3f3f3; } .mfp-figure small { color: #bdbdbd; display: block; font-size: 12px; line-height: 14px; } .mfp-image-holder .mfp-content { max-width: 100%; } .mfp-gallery .mfp-image-holder .mfp-figure { cursor: pointer; } @media screen and (max-width: 800px) and (orientation: landscape), screen and (max-height: 300px) { /** * Remove all paddings around the image on small screen */ .mfp-img-mobile .mfp-image-holder { padding-left: 0; padding-right: 0; } .mfp-img-mobile img.mfp-img { padding: 0; } /* The shadow behind the image */ .mfp-img-mobile .mfp-figure:after { top: 0; bottom: 0; } .mfp-img-mobile .mfp-bottom-bar { background: rgba(0, 0, 0, 0.6); bottom: 0; margin: 0; top: auto; padding: 3px 5px; position: fixed; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .mfp-img-mobile .mfp-bottom-bar:empty { padding: 0; } .mfp-img-mobile .mfp-counter { right: 5px; top: 3px; } .mfp-img-mobile .mfp-close { top: 0; right: 0; width: 35px; height: 35px; line-height: 35px; background: rgba(0, 0, 0, 0.6); position: fixed; text-align: center; padding: 0; } .mfp-img-mobile .mfp-figure small { display: inline; margin-left: 5px; } } @media all and (max-width: 800px) { .mfp-arrow { -webkit-transform: scale(0.75); transform: scale(0.75); } .mfp-arrow-left { -webkit-transform-origin: 0; transform-origin: 0; } .mfp-arrow-right { -webkit-transform-origin: 100%; transform-origin: 100%; } .mfp-container { padding-left: 6px; padding-right: 6px; } } .mfp-ie7 .mfp-img { padding: 0; } .mfp-ie7 .mfp-bottom-bar { width: 600px; left: 50%; margin-left: -300px; margin-top: 5px; padding-bottom: 5px; } .mfp-ie7 .mfp-container { padding: 0; } .mfp-ie7 .mfp-content { padding-top: 44px; } .mfp-ie7 .mfp-close { top: 0; right: 0; padding-top: 0; } /** * Fade-zoom animation for first dialog */ /* start state */ .mfp-fade .zoom-anim-dialog { opacity: 0; .transition(all 0.2s ease-in-out); .scale(0.8); } /* animate in */ .mfp-fade.mfp-ready .zoom-anim-dialog { opacity: 1; .scale(1); } /* animate out */ .mfp-fade.mfp-removing .zoom-anim-dialog { .scale(0.8); opacity: 0; } /* Dark overlay, start state */ .mfp-fade.mfp-bg { opacity: 0; .transition(opacity 0.3s ease-out); } /* animate in */ .mfp-fade.mfp-ready.mfp-bg { opacity: 0.8; } /* animate out */ .mfp-fade.mfp-removing.mfp-bg { opacity: 0; } ================================================ FILE: assets/less/main.less ================================================ /*! // =========================================================== // HPSTR Jekyll Theme // By: Michael Rose // =========================================================== */ // ROOT ======================================================= // CSS Reset ================================================== @import "reset.less"; // Variables and mixins ======================================= @import "variables.less"; // Site wide styles (html, body, global classes) ============== @import "site.less"; // TYPOGRAPHY ================================================= @import "typography.less"; // Pygments Syntax highlighting =============================== @import "pygments.less"; // Coderay Syntax highlighting ================================ @import "coderay.less"; // Gist Syntax highlighting =================================== @import "gist.less"; // MIXINS ===================================================== @import "mixins.less"; @import "grid.less"; // ELEMENTS =================================================== // Figures, images, social media, other elements ============== @import "elements.less"; // Drop down menu @import "dl-menu.less"; // Font Awesome webfont icons ================================= @import "font-awesome/font-awesome.less"; // Magnific Popup ============================================= @import "magnific-popup.less"; // LAYOUT ===================================================== // Page level layout styles =================================== @import "page.less"; ================================================ FILE: assets/less/mixins.less ================================================ // UTILITY MIXINS // -------------------------------------------------- // Clearfix // -------------------- // For clearing floats like a boss h5bp.com/q .clearfix { *zoom: 1; &:before, &:after { display: table; content: ""; // Fixes Opera/contenteditable bug: // http://nicolasgallagher.com/micro-clearfix-hack/#comment-36952 line-height: 0; } &:after { clear: both; } } // Webkit-style focus // -------------------- .tab-focus() { // Default outline: thin dotted #333; // Webkit outline: 5px auto -webkit-focus-ring-color; outline-offset: -2px; } // Center-align a block level element // ---------------------------------- .center-block() { display: block; margin-left: auto; margin-right: auto; } // TYPOGRAPHY // -------------------------------------------------- // Full-fat vertical rhythm // ------------------------ .font-size(@size) { font-size: 0px + @size; font-size: 0rem + @size / @doc-font-size; line-height: 0 + round(@doc-line-height / @size*10000) / 10000; margin-bottom: 0px + @doc-line-height; margin-bottom: 0rem + (@doc-line-height / @doc-font-size); } // Just the REMs // ------------- .font-rem(@size) { font-size: 0px + @size; font-size: 0rem + @size / @doc-font-size; } // Just font-size and line-height // ------------------------------ .font(@size) { font-size: 0px + @size; font-size: 0rem + @size / @doc-font-size; line-height: 0 + round(@doc-line-height / @size*10000) / 10000; } .text-overflow() { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } // GRADIENTS // -------------------------------------------------- .horizontal(@startColor : @white, @endColor : @lightergrey) { background-color: @endColor; background-image : -webkit-gradient(linear, 0 0, 100% 0, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+ background-image : -webkit-linear-gradient(left, @startColor, @endColor); // Safari 5.1+, Chrome 10+ background-image : -moz-linear-gradient(left, @startColor, @endColor); // FF 3.6+ background-image : -ms-linear-gradient(left, @startColor, @endColor); // IE10 background-image : -o-linear-gradient(left, @startColor, @endColor); // Opera 11.10 background-image : linear-gradient(left, @startColor, @endColor); // W3C background-repeat : repeat-x; } .vertical(@startColor : @white, @endColor: @lightergrey) { background-image : -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+ background-image : -webkit-linear-gradient(top, @startColor, @endColor); // Safari 5.1+, Chrome 10+ background-color : @endColor; background-image : -moz-linear-gradient(top, @startColor, @endColor); // FF 3.6+ background-image : -ms-linear-gradient(top, @startColor, @endColor); // IE10 background-image : -o-linear-gradient(top, @startColor, @endColor); // Opera 11.10 background-image : linear-gradient(top, @startColor, @endColor); // W3C background-repeat : repeat-x; } .directional(@startColor : @white, @endColor : @lightergrey, @deg : 45deg) { background-color : @endColor; background-image : -moz-linear-gradient(@deg, @startColor, @endColor); // FF 3.6+ background-image : -ms-linear-gradient(@deg, @startColor, @endColor); // IE10 background-image : -webkit-linear-gradient(@deg, @startColor, @endColor); // Safari 5.1+, Chrome 10+ background-image : -o-linear-gradient(@deg, @startColor, @endColor); // Opera 11.10 background-image : linear-gradient(@deg, @startColor, @endColor); // W3C background-repeat : repeat-x; } // .bordered(COLOR, COLOR, COLOR, COLOR); .bordered(@top-color: #eee, @right-color: #eee, @bottom-color: #eee, @left-color: #eee) { border-top : solid 1px @top-color; border-left : solid 1px @left-color; border-right : solid 1px @right-color; border-bottom : solid 1px @bottom-color; } // ROUND CORNERS // -------------------------------------------------- // .rounded(VALUE); .rounded(@radius:4px) { -webkit-border-radius : @radius; -moz-border-radius : @radius; border-radius : @radius; } // .border-radius(VALUE,VALUE,VALUE,VALUE); .border-radius(@topright: 0, @bottomright: 0, @bottomleft: 0, @topleft: 0) { -webkit-border-top-right-radius : @topright; -webkit-border-bottom-right-radius : @bottomright; -webkit-border-bottom-left-radius : @bottomleft; -webkit-border-top-left-radius : @topleft; -moz-border-radius-topright : @topright; -moz-border-radius-bottomright : @bottomright; -moz-border-radius-bottomleft : @bottomleft; -moz-border-radius-topleft : @topleft; border-top-right-radius : @topright; border-bottom-right-radius : @bottomright; border-bottom-left-radius : @bottomleft; border-top-left-radius : @topleft; -webkit-background-clip : padding-box; -moz-background-clip : padding; background-clip : padding-box; } // .box-shadow(HORIZONTAL VERTICAL BLUR COLOR)) .box-shadow(@shadow: 0 1px 3px rgba(0,0,0,.25)) { -webkit-box-shadow : @shadow; -moz-box-shadow : @shadow; box-shadow : @shadow; } // .drop-shadow(HORIZONTAL, VERTICAL, BLUR, ALPHA); .drop-shadow(@x-axis: 0, @y-axis: 1px, @blur: 2px, @alpha: 0.1) { -webkit-box-shadow : @x-axis @y-axis @blur rgba(0, 0, 0, @alpha); -moz-box-shadow : @x-axis @y-axis @blur rgba(0, 0, 0, @alpha); box-shadow : @x-axis @y-axis @blur rgba(0, 0, 0, @alpha); } // .text-shadow(); .text-shadow(@shadow: 0 2px 3px rgba(0,0,0,.25)) { text-shadow : @shadow; } // .opacity(VALUE); .opacity(@opacity : .5) { -webkit-opacity : @opacity; -moz-opacity : @opacity; opacity : @opacity; } // TRANSFORMATIONS // -------------------------------------------------- // .rotate(VALUEdeg); .rotate(@deg) { -webkit-transform : rotate(@deg); -moz-transform : rotate(@deg); -ms-transform : rotate(@deg); -o-transform : rotate(@deg); transform : rotate(@deg); } // .scale(VALUE); .scale(@ratio) { -webkit-transform : scale(@ratio); -moz-transform : scale(@ratio); -ms-transform : scale(@ratio); -o-transform : scale(@ratio); transform : scale(@ratio); } // .skew(VALUE, VALUE); .skew(@x: 0, @y: 0) { -webkit-transform : skew(@x, @y); -moz-transform : skew(@x, @y); -ms-transform : skew(@x, @y); -o-transform : skew(@x, @y); transform : skew(@x, @y); } // .transition(PROPERTY DURATION DELAY(OPTIONAL) TIMING-FINCTION); .transition(@transition) { -webkit-transition : @transition; -moz-transition : @transition; -ms-transition : @transition; -o-transition : @transition; transition : @transition; } // .translate(VALUE, VALUE) .translate(@x: 0, @y: 0) { -webkit-transform : translate(@x, @y); -moz-transform : translate(@x, @y); -ms-transform : translate(@x, @y); -o-transform : translate(@x, @y); transform : translate(@x, @y); } .translate3d(@x: 0, @y: 0, @z: 0) { -webkit-transform : translate(@x, @y, @z); -moz-transform : translate(@x, @y, @z); -ms-transform : translate(@x, @y, @z); -o-transform : translate(@x, @y, @z); transform : translate(@x, @y, @z); } .animation(@name, @duration: 300ms, @delay: 0, @ease: ease) { -webkit-animation: @name @duration @delay @ease; -moz-animation: @name @duration @delay @ease; -ms-animation: @name @duration @delay @ease; } // BACKGROUND // -------------------------------------------------- // .background-alpha(VALUE VALUE); .background-alpha(@color: @white, @alpha: 1) { background-color : hsla(hue(@color), saturation(@color), lightness(@color), @alpha); } // .background-size(VALUE VALUE); .background-size(@size){ -webkit-background-size : @size; -moz-background-size : @size; -o-background-size : @size; background-size : @size; } // .background-clip(VALUE); (border-box, padding-box, content-box) .background-clip(@clip) { -webkit-background-clip : @clip; -moz-background-clip : @clip; background-clip : @clip; } // .box-sizing(VALUE); (border-box, padding-box, content-box) .box-sizing(@boxsize: border-box) { -webkit-box-sizing : @boxsize; -moz-box-sizing : @boxsize; -ms-box-sizing : @boxsize; box-sizing : @boxsize; } // For image replacement .hide-text() { text-indent : 100%; white-space : nowrap; overflow : hidden; } // Hide from visual and speaking browsers .hidden() { display : none !important; visibility : hidden; } .hidden { display: none; visibility: hidden; } // Hide but maintain layout .invisible() { visibility : hidden; } // .resize(VALUE) (none, both, horizontal, vertical, inherit) .resize(@direction: both) { resize : @direction; overflow : auto; } // .userselect(VALUE) (all, element, none, text) .user-select(@select) { -webkit-user-select : @select; -moz-user-select : @select; -o-user-select : @select; user-select : @select; } // Hidden but available to speaking browsers .visuallyhidden() { overflow : hidden; position : absolute; clip : rect(0 0 0 0); height : 1px; width : 1px; margin : -1px; padding : 0; border : 0; } // Make visuallyhidden focusable with a keyboard .visuallyhidden.focusable:active, .visuallyhidden.focusable:focus { position : static; clip : auto; height : auto; width : auto; margin : 0; overflow: visible; } // MEDIA QUERIES // -------------------------------------------------- @small: ~"only screen and (min-width: 30em)"; @medium: ~"only screen and (min-width: 48em)"; @large: ~"only screen and (min-width: 62.5em)"; @highdensity: ~"only screen and (-webkit-min-device-pixel-ratio: 1.5)", ~"only screen and (-o-min-device-pixel-ratio: 3/2)", ~"only screen and (min-resolution: 144dpi)", ~"only screen and (min-resolution: 1.5dppx)"; ================================================ FILE: assets/less/page.less ================================================ body { margin: 0; padding: 0; width: 100%; background-color: @body-color; } // Main // -------------------------------------------------- .entry, .hentry { .clearfix(); } .entry-content { .font-size(16); // Dotted line underlines for links p > a, li > a { border-bottom: 1px dotted lighten(@link-color, 50); &:hover { border-bottom-style: solid; } } } // Entry Header // -------------------------------------------------- .entry-header { width: 100%; overflow: hidden; position: relative; } .header-title { text-align: center; margin: 30px 0 0; h1 { margin: 10px 20px; font-weight: 700; .font-rem(28); color: lighten(@base-color,20); @media @medium { .font-rem(48); } @media @large { .font-rem(60); } } h2 { margin: 0; .font-rem(18); text-transform: uppercase; color: lighten(@base-color,40); @media @medium { .font-rem(30); } } p { color: lighten(@base-color,20); } } .feature .header-title { position: absolute; top: 0; display: table; margin-top: 0; width: 100%; height: 400px; overflow: hidden; .header-title-wrap { display: table-cell; vertical-align: middle; margin: 0 auto; text-align: center; } h1 { margin: 10px; font-weight: 700; margin: 10px 60px; color: @white; text-shadow: 1px 1px 4px fade(@base-color,60); a { color: @white; } @media @large { } } h2 { margin: 0; color: @white; text-transform: uppercase; @media @medium { a { color: @white; } } } p { color: @white; } } .entry-image { position: relative; top: -50%; left: -50%; width: 200%; height: 200%; min-height: 400px; overflow: hidden; &:after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: fade(@base-color,30); } img { position: absolute; top: 0; left: 0; right: 0; bottom: 0; margin: auto; min-width: 50%; min-height: 50%; } } // Feature Image Caption .image-credit { position: absolute; bottom: 0; right: 0; margin: 0 auto; max-width: 440px; padding: 10px 15px; background-color: fade(@base-color,50); color: @white; .font-rem(12); text-align: right; .border-radius(3px,0,0,3px); z-index: 10; @media @medium { max-width: 760px; } @media @large { max-width: 960px; } a { color: @white; text-decoration: none; } } // Single Post and Page // -------------------------------------------------- .entry-meta { .font-rem(12); text-transform: uppercase; color: lighten(@base-color,60); a { color: lighten(@base-color,60); } .vcard { &:before { content: " by "; } } .tag { display: inline-block; margin: 4px; padding: 2px 6px; background-color: lighten(@base-color,60); color: @white; .rounded(3px); color: @white; span { vertical-align: super; .font-rem(10); } &:hover { background-color: lighten(@base-color,50); } &:active { .translate(0, 1px); .box-shadow(0 0 1px fade(@base-color, 20)); } } } #post, #page { .entry-content { margin: 40px 2px 20px 2px; padding: 10px 15px; background-color: @white; box-shadow: 0 0 0 0, 0 6px 12px fade(@black,10); .rounded(3px); @media @medium { margin-left: 10px; margin-right: 10px; padding: 20px 30px; } @media @large { max-width: 800px; margin: 50px auto 30px auto; padding: 50px 80px; > p:first-child { .font-size(20); } } } #disqus_thread { margin: 40px 2px 20px 2px; padding: 10px 15px; background-color: @white; box-shadow: 0 0 0 1px fade(@border-color,10), 0 6px 12px fade(@black,10); .rounded(3px); @media @medium { margin-left: 10px; margin-right: 10px; padding: 20px 30px; } @media @large { max-width: 800px; padding: 50px 80px; margin: 0 auto 30px auto; } } .entry-meta { margin: 50px 30px 30px; text-align: center; } } .entry-tags { display: block; margin-bottom: 6px; } // Post Pagination Module .pagination { margin: 20px 10px; text-align: center; ul { display: inline; margin-left: 10px; margin-right: 10px; } li { padding-left: 4px; padding-right: 4px; } .current-page { font-weight: 700; } } // Read More Module .read-more { position: relative; margin: 40px 2px 20px 2px; padding: 40px 15px 25px; background-color: @white; box-shadow: 0 0 0 1px fade(@border-color,10), 0 6px 12px fade(@black,10); .rounded(3px); @media @medium { margin: 50px 10px 20px 10px; padding: 50px 40px 25px; } @media @large { max-width: 800px; padding: 50px 80px; margin: 60px auto; } text-align: center; .clearfix(); } .read-more-header { position: absolute; top: -20px; left: 0; right: 0; height: 35px; a { .btn(); } } .read-more-content { .font-size(16); // Dotted line underlines for links p > a, li > a { border-bottom: 1px dotted lighten(@link-color, 50); &:hover { border-bottom-style: solid; } } h3 { margin: 0; .font-rem(28); a { color: @primary; } @media @medium { .font-rem(36); } } } .read-more-list { border-top: solid 1px lighten(@base-color,60); } .list-item { width: 100%; text-align: left; h4 { .font-rem(18); margin-bottom: 0; } span { display: block; .font-rem(14); color: lighten(@base-color,50); } @media @medium { width: 49%; float: left; &:nth-child(2) { text-align: right; } } } // Post Index // -------------------------------------------------- #post-index { #main { margin: 40px 2px 20px 2px; @media @medium { margin-left: 20px; margin-right: 20px; } @media @large { max-width: 800px; margin-top: 50px; margin-left: auto; margin-right: auto; } } article { background-color: @white; box-shadow: 0 0 0 0, 0 6px 12px fade(@base-color,10); .rounded(3px); margin-bottom: 20px; padding: 25px 15px; @media @medium { padding: 30px; } @media @large { margin-bottom: 30px; padding: 50px 80px; } } } // Footer // -------------------------------------------------- .footer-wrapper { .clearfix(); margin: 2em auto; text-align: center; color: lighten(@text-color,20); a { color: lighten(@text-color,20); } } // Social Share // -------------------------------------------------- .socialcount { .font-rem(16); font-weight: 700; li { padding-left: 10px; padding-right: 10px; } p > a, li > a { border-bottom-width: 0; } } // Browser Upgrade // -------------------------------------------------- .upgrade { padding: 10px; text-align: center; } // Google Search // -------------------------------------------------- #goog-fixurl { ul { list-style: none; margin-left: 0; padding-left: 0; li { list-style-type: none; } } } #goog-wm-qt { width: auto; margin-right: 10px; margin-bottom: 20px; padding: 8px 20px; display: inline-block; .font-rem(14); background-color: @white; color: @primary; border-width: 2px !important; border-style: solid !important; border-color: lighten(@primary,50); .rounded(3px); } #goog-wm-sb { .btn(); } ================================================ FILE: assets/less/pygments.less ================================================ .highlight { margin-bottom: 1.5em; .font(12); color: #d0d0d0; border: 1px solid darken(@body-color, 5); background-color: #272822; .rounded(3px); pre { position: relative; margin: 0; padding: 1em; } .lineno { padding-right: 24px; color: #8f908a;} .hll { background-color: #49483e } .c { color: #75715e } /* Comment */ .err { color: #960050; background-color: #1e0010 } /* Error */ .k { color: #66d9ef } /* Keyword */ .l { color: #ae81ff } /* Literal */ .n { color: #f8f8f2 } /* Name */ .o { color: #f92672 } /* Operator */ .p { color: #f8f8f2 } /* Punctuation */ .cm { color: #75715e } /* Comment.Multiline */ .cp { color: #75715e } /* Comment.Preproc */ .c1 { color: #75715e } /* Comment.Single */ .cs { color: #75715e } /* Comment.Special */ .ge { font-style: italic } /* Generic.Emph */ .gs { font-weight: bold } /* Generic.Strong */ .kc { color: #66d9ef } /* Keyword.Constant */ .kd { color: #66d9ef } /* Keyword.Declaration */ .kn { color: #f92672 } /* Keyword.Namespace */ .kp { color: #66d9ef } /* Keyword.Pseudo */ .kr { color: #66d9ef } /* Keyword.Reserved */ .kt { color: #66d9ef } /* Keyword.Type */ .ld { color: #e6db74 } /* Literal.Date */ .m { color: #ae81ff } /* Literal.Number */ .s { color: #e6db74 } /* Literal.String */ .na { color: #a6e22e } /* Name.Attribute */ .nb { color: #f8f8f2 } /* Name.Builtin */ .nc { color: #a6e22e } /* Name.Class */ .no { color: #66d9ef } /* Name.Constant */ .nd { color: #a6e22e } /* Name.Decorator */ .ni { color: #f8f8f2 } /* Name.Entity */ .ne { color: #a6e22e } /* Name.Exception */ .nf { color: #a6e22e } /* Name.Function */ .nl { color: #f8f8f2 } /* Name.Label */ .nn { color: #f8f8f2 } /* Name.Namespace */ .nx { color: #a6e22e } /* Name.Other */ .py { color: #f8f8f2 } /* Name.Property */ .nt { color: #f92672 } /* Name.Tag */ .nv { color: #f8f8f2 } /* Name.Variable */ .ow { color: #f92672 } /* Operator.Word */ .w { color: #f8f8f2 } /* Text.Whitespace */ .mf { color: #ae81ff } /* Literal.Number.Float */ .mh { color: #ae81ff } /* Literal.Number.Hex */ .mi { color: #ae81ff } /* Literal.Number.Integer */ .mo { color: #ae81ff } /* Literal.Number.Oct */ .sb { color: #e6db74 } /* Literal.String.Backtick */ .sc { color: #e6db74 } /* Literal.String.Char */ .sd { color: #e6db74 } /* Literal.String.Doc */ .s2 { color: #e6db74 } /* Literal.String.Double */ .se { color: #ae81ff } /* Literal.String.Escape */ .sh { color: #e6db74 } /* Literal.String.Heredoc */ .si { color: #e6db74 } /* Literal.String.Interpol */ .sx { color: #e6db74 } /* Literal.String.Other */ .sr { color: #e6db74 } /* Literal.String.Regex */ .s1 { color: #e6db74 } /* Literal.String.Single */ .ss { color: #e6db74 } /* Literal.String.Symbol */ .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ .vc { color: #f8f8f2 } /* Name.Variable.Class */ .vg { color: #f8f8f2 } /* Name.Variable.Global */ .vi { color: #f8f8f2 } /* Name.Variable.Instance */ .il { color: #ae81ff } /* Literal.Number.Integer.Long */ } ================================================ FILE: assets/less/reset.less ================================================ // // Reset CSS // Adapted from http://github.com/necolas/normalize.css // -------------------------------------------------- *, *:after, *:before { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } // Display in IE6-9 and FF3 // ------------------------- article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; } // Display block in IE6-9 and FF3 // ------------------------- audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; } // Prevents modern browsers from displaying 'audio' without controls // ------------------------- audio:not([controls]) { display: none; } // Base settings // ------------------------- html { font-size: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } // Focus states a:focus { .tab-focus(); } // Hover & Active a:hover, a:active { outline: 0; } // Prevents sub and sup affecting line-height in all browsers // ------------------------- sub, sup { position: relative; font-size: 75%; line-height: 0; vertical-align: baseline; } sup { top: -0.5em; } sub { bottom: -0.25em; } // Blockquote // ------------------------- blockquote { margin: 0; } // Img border in a's and image quality // ------------------------- img { /* Responsive images (ensure images don't scale beyond their parents) */ max-width: 100%; /* Part 1: Set a maxium relative to the parent */ width: auto\9; /* IE7-8 need help adjusting responsive images */ height: auto; /* Part 2: Scale the height according to the width, otherwise you get stretching */ vertical-align: middle; border: 0; -ms-interpolation-mode: bicubic; } // Prevent max-width from affecting Google Maps #map_canvas img, .google-maps img { max-width: none; } // Forms // ------------------------- // Font size in all browsers, margin changes, misc consistency button, input, select, textarea { margin: 0; font-size: 100%; vertical-align: middle; } button, input { *overflow: visible; // Inner spacing ie IE6/7 line-height: normal; // FF3/4 have !important on line-height in UA stylesheet } button::-moz-focus-inner, input::-moz-focus-inner { // Inner padding and border oddities in FF3/4 padding: 0; border: 0; } button, html input[type="button"], // Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` and `video` controls. input[type="reset"], input[type="submit"] { -webkit-appearance: button; // Corrects inability to style clickable `input` types in iOS. cursor: pointer; // Improves usability and consistency of cursor style between image-type `input` and others. } label, select, button, input[type="button"], input[type="reset"], input[type="submit"], input[type="radio"], input[type="checkbox"] { cursor: pointer; // Improves usability and consistency of cursor style between image-type `input` and others. } input[type="search"] { // Appearance in Safari/Chrome .box-sizing(content-box); -webkit-appearance: textfield; } input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; // Inner-padding issues in Chrome OSX, Safari 5 } textarea { overflow: auto; // Remove vertical scrollbar in IE6-9 vertical-align: top; // Readability and alignment cross-browser } // Printing // ------------------------- // Source: https://github.com/h5bp/html5-boilerplate/blob/master/css/main.css @media print { * { text-shadow: none !important; color: #000 !important; // Black prints faster: h5bp.com/s background: transparent !important; box-shadow: none !important; } a, a:visited { text-decoration: underline; } a[href]:after { content: " (" attr(href) ")"; } abbr[title]:after { content: " (" attr(title) ")"; } // Don't show links for images, or javascript/internal links .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } thead { display: table-header-group; // h5bp.com/t } tr, img { page-break-inside: avoid; } img { max-width: 100% !important; } @page { margin: 0.5cm; } p, h2, h3 { orphans: 3; widows: 3; } h2, h3 { page-break-after: avoid; } } ================================================ FILE: assets/less/site.less ================================================ // Selection // -------------------------------------------------- ::-moz-selection { background-color: lighten(@base-color, 65%); color: @base-color; text-shadow: none; } ::selection { background-color: lighten(@base-color, 65%); color: @base-color; text-shadow: none; } // Global Classes // -------------------------------------------------- .wrap { margin: 0 auto; } .all-caps { text-transform: uppercase; } .pull-left { float: left; } .pull-right { float:right; } .unstyled-list { list-style: none; margin-left: 0; padding-left: 0; li { list-style-type: none; } } .inline-list { list-style: none; margin-left: 0; padding-left: 0; li { list-style-type: none; display: inline; } } // Global Transition // --------------------------------------------------- b, i, strong, em, blockquote, p, q, span, figure, img, h1, h2, header, input, a { .transition(all .2s ease); } ================================================ FILE: assets/less/typography.less ================================================ // Body // -------------------------------------------------- body { font-family: @base-font; color: @text-color; } // Headings // -------------------------------------------------- h1, h2, h3, h4, h5, h6 { font-family: @heading-font; } h1 { .font-rem(32); } // Links // -------------------------------------------------- a { text-decoration: none; color: @link-color; &:visited { color: lighten(@link-color, 20); } &:hover { color: darken(@link-color, 20); } &:focus { outline: thin dotted; color: darken(@link-color, 20); } &:hover, &:active { outline: 0; } } .link-arrow { font-weight: 100; text-decoration: underline; font-style: normal; } // Figures // -------------------------------------------------- figcaption { padding-top: 10px; .font(14); line-height: 1.3; color: lighten(@text-color, 10); } // Note text // -------------------------------------------------- .notice { margin-top: 1.5em; padding: .5em 1em; text-indent: 0; .font-rem(14); background-color: @body-color; border: 1px solid darken(@body-color,20); .rounded(3px); } // Blockquotes // -------------------------------------------------- blockquote { font-family: @alt-font; font-style: italic; border-left: 8px solid @border-color; padding-left: 20px; @media @medium { margin-left: -28px; } } // Footnotes // -------------------------------------------------- .entry-content .footnotes { ol, li, p { .font-size(14); } } // Code // -------------------------------------------------- tt, code, kbd, samp, pre { font-family: @code-font; } p, li { code { .font-rem(12); line-height: 1.5; white-space: nowrap; margin: 0 2px; padding: 0 5px; border: 1px solid lighten(@black, 90); background-color: lighten(@black, 95); .rounded(3px); } } pre { .font-rem(12); line-height: 1.5; overflow-x: auto; &::-webkit-scrollbar { height: 12px; background-color: #34362e; border-radius: 0 0 4px 4px; } &::-webkit-scrollbar-thumb:horizontal { background-color: #6a6d5d; .rounded(4px); } } ================================================ FILE: assets/less/variables.less ================================================ // Typography // -------------------------------------------------- @base-font: 'Lato', Calibri, Arial, sans-serif; @heading-font: @base-font; @caption-font: @base-font; @code-font: monospace; @alt-font: serif; @doc-font-size: 16; @doc-line-height: 26; // set-up the body font-size / line-height body { margin-top: 0px + @doc-line-height; font-size: 0px + @doc-font-size; } // Colors // -------------------------------------------------- @base-color : #222; @body-color : #e8e8e8; @text-color : #222; @comp-color : spin(@base-color, 180); @border-color : lighten(@base-color,60); @white : #fff; @black : #000; @link-color : #222; @primary : @base-color; @success : #5cb85c; @warning : #dd8338; @danger : #C64537; @info : #308cbc; ================================================ FILE: common/js/ajax.js ================================================ function Ajax(parametObject) { var xhr, responseType, defineParam, method, url, data; defineParam = { method : "GET", type : "json" } url = parametObject.url; data = parametObject.data; responseType = parametObject.type || defineParam.type; method = parametObject.method || defineParam.method; if(navigator.userAgent.toUpperCase().indexOf('MSIE') >= 0){ try{ xhr=new ActiveXObject('microsoft.xmlhttp'); }catch(e){ xhr=new ActiveXObject('msxml2.xmlhttp'); } }else{ xhr=new XMLHttpRequest(); }; xhr.onreadystatechange = function() { if(xhr.readyState == 4) { if(responseType == "json") { responseType = xhr.responseText; }else { responseType = xhr.responseXML; } if(xhr.status== 200 && typeof(parametObject.success) == 'function') { parametObject.success(responseType); }else if(xhr.status != 200 && typeof(parametObject.error) == 'function') { parametObject.error(responseType); } } } if(method.toUpperCase() == "GET") { if(data) { url = url + '?' + data + '&random=' +Math.random(); } xhr.open('GET', url, true); xhr.send(null); } if(method.toUpperCase() == "POST") { xhr.open('POST', url, true); xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded"); xhr.send(data); } } ================================================ FILE: common/js/startmove.js ================================================ function startMove(obj, json, fnEnd) { if (obj.timer) { clearInterval(obj.timer); } obj.timer = setInterval(function() { doMove(obj, json, fnEnd); }, 10); var oDate = new Date(); if (oDate.getTime() - obj.lastMove > 30) { doMove(obj, json, fnEnd); } } function getStyle(obj, attr){ return obj.currentStyle?obj.currentStyle[attr]:getComputedStyle(obj, false)[attr]; } function doMove(obj, json, speed, fnEnd) { var iCur = 0; var attr = ''; var _speed = speed || 8; var bStop = true; //假设运动已经该停止了 for (attr in json) { iCur = attr == 'opacity' ? parseInt(100 * parseFloat(getStyle(obj, 'opacity'))) : parseInt(getStyle(obj, attr)); if (isNaN(iCur)) { iCur = 0; } var iSpeed = (json[attr] - iCur) / _speed; iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed); if (parseInt(json[attr]) != iCur) { bStop = false; } if (attr == 'opacity') { obj.style.filter = "alpha(opacity:" + (iCur + iSpeed) + ")"; obj.style.opacity = (iCur + iSpeed) / 100; } else { obj.style[attr] = iCur + iSpeed + 'px'; } } if (bStop) { clearInterval(obj.timer); obj.timer = null; if (fnEnd) { fnEnd(); } } obj.lastMove = (new Date()).getTime(); } ================================================ FILE: common/js/test.js ================================================ test.js ================================================ FILE: demo/2014-11/ali-media.html ================================================ JS Bin

    阿里巴巴面试题

    • 前段工程面试题
    • 设计师面试题
    • JAVA面试题
    我是标题一标题二标题三
    内容内容内容
    ================================================ FILE: demo/2014-11/ali-percent.html ================================================ 流式布局

    阿里巴巴面试题

    • 前段工程面试题
    • 设计师面试题
    • JAVA面试题
    我是标题一标题二标题三
    内容内容内容
    ================================================ FILE: demo/2014-11/ali-px.html ================================================ JS Bin

    阿里巴巴面试题

    • 前段工程面试题
    • 设计师面试题
    • JAVA面试题
    我是标题一标题二标题三
    内容内容内容
    ================================================ FILE: demo/2014-11/ali-rem.html ================================================ 弹性布局

    阿里巴巴面试题

    • 前段工程面试题
    • 设计师面试题
    • JAVA面试题
    我是标题一标题二标题三
    内容内容内容
    ================================================ FILE: demo/absolute_percent.html ================================================



    ================================================ FILE: demo/cqut-paging/demo.html ================================================

    页数小于10 iPage(document.getElementsByTagName("div")[0],10,1)

    页数大于10且当前页小于5 iPage(document.getElementsByTagName("div")[1],13,5);

    页数大于10且当前页大于5 iPage(document.getElementsByTagName("div")[1],13,9);

    ================================================ FILE: demo/css3/imocoo_2.html ================================================ CSS3 Full Background Slider ================================================ FILE: demo/javascript-seamless-handover/css/main.css ================================================ .wrap{ width:500px;font-size:22px; margin:20px auto 0; padding:40px 20px;} .list{ padding:0 60px;} .list li{list-style:decimal; line-height:50px;border-bottom: 1px solid #f2f2f2;} .list li a{ display:block; padding:5px 8px;transition:all 0.6s;} .list li a:hover{ text-indent:25px;border-radius:5px;color: #fff;text-shadow:0 2px 1px #360;background-color: #393;*background-color:#51a351;background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(to bottom,#62c462,#51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff51a351',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);text-decoration:none;} ================================================ FILE: demo/javascript-seamless-handover/css/reset.css ================================================ *{ margin:0; padding:0;} body{font:14px/1.125 Arial,Helvetica,sans-serif;background:#fff;} table{border-collapse:collapse;border-spacing:0;} li{list-style:none;} fieldset,img{border:0;} article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block} q:before,q:after{content:'';} a:focus,input,textarea{outline-style:none;} input[type="text"],input[type="password"],textarea{outline-style:none;-webkit-appearance:none;} textarea{resize:none} address,caption,cite,code,dfn,em,i,th,var{font-style:normal;font-weight:normal;} legend{color:#000;} abbr,acronym{border:0;font-variant:normal;} a{color:#0a8cd2;text-decoration:none;} a:hover{text-decoration:underline;} .clearfix:after{content:".";display:block;height:0;clear:both;visibility:hidden;} .clearfix{display:inline-block;} .clearfix{display:block;} .clear{clear:both;height:0;font:0/0 Arial;visibility:hidden;} .none{display:none} .shadow{border:1px solid #e6e6e6;border-radius:5px;box-shadow:0px 2px 3px #ececec;background: #fff;} ================================================ FILE: demo/javascript-seamless-handover/index.html ================================================ test
      ================================================ FILE: demo/javascript-seamless-handover/slide/bd01.html ================================================ 百度爱玩 - 图片轮换效果
      • 开始游戏
        开始游戏
        剑宗
        1

        剑宗

        角色扮演

        大型3D武侠修仙网页游戏,全视角3D战斗!

      • 开始游戏
        开始游戏
        剑宗
        1

        剑宗

        角色扮演

        大型3D武侠修仙网页游戏,全视角3D战斗!

      • 开始游戏
        开始游戏
        剑宗
        1

        剑宗

        角色扮演

        大型3D武侠修仙网页游戏,全视角3D战斗!

      • 开始游戏
        开始游戏
        剑宗
        1

        剑宗

        角色扮演

        大型3D武侠修仙网页游戏,全视角3D战斗!

      • 开始游戏
        开始游戏
        剑宗
        1

        剑宗

        角色扮演

        大型3D武侠修仙网页游戏,全视角3D战斗!

      • 开始游戏
        开始游戏
        剑宗
        1

        剑宗

        角色扮演

        大型3D武侠修仙网页游戏,全视角3D战斗!

      ================================================ FILE: demo/javascript-seamless-handover/slide/css/slide.css ================================================ /* reset */ *{ margin:0; padding:0;} body{font:12px/1.125 Arial,Helvetica,sans-serif;background:#fff;} table{border-collapse:collapse;border-spacing:0;} li{list-style:none;} fieldset,img{border:0;} article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block} q:before,q:after{content:'';} a:focus,input,textarea{outline-style:none;} input[type="text"],input[type="password"],textarea{outline-style:none;-webkit-appearance:none;} textarea{resize:none} address,caption,cite,code,dfn,em,i,th,var{font-style:normal;font-weight:normal;} legend{color:#000;} abbr,acronym{border:0;font-variant:normal;} a{color:#0a8cd2;text-decoration:none;} a:hover{text-decoration:underline;} .clearfix:after{content:".";display:block;height:0;clear:both;visibility:hidden;} .clearfix{display:inline-block;} .clearfix{display:block;} .clear{clear:both;height:0;font:0/0 Arial;visibility:hidden;} .none{display:none} /* main */ .mp-banner{position:relative;height:303px} .mp-banner-wrap{position:relative;width:980px;margin:0 auto;height:303px;} .mp-banner-wrap .mp-banner-inner{position:relative;width:757px;height:303px;overflow:hidden} .mp-banner-ct{position:absolute;top:20px;left:0;} .mp-banner-item{position:relative;float:left;width:237px;height:283px;margin-right:23px} .mp-banner-item:hover .mp-banner-back{height:283px;z-index:2} .mp-banner-item .show{z-index:2;height:283px} .mp-banner-back,.mp-banner-front,.mp-banner-overlay{position:absolute;width:237px;height:283px;top:0;left:0} .mp-banner-overlay{background:#000;opacity:.7;filter:alpha(opacity=70);z-index:-1} .mp-banner-front{_overflow:hidden} .mp-banner-back{color:#fff;z-index:0;height:0;overflow:hidden} .mp-banner-back .mp-btn-start{position:absolute;top:182px;right:0;width:75px;height:28px;line-height:28px;background:#ff7a01;border:1px solid #d45f00;text-align:center} .mp-banner-link{position:absolute;top:0;left:0;width:100%;height:234px;border-radius:5px;z-index:9;background:url(../images/transparent.gif);text-indent:-9999px;overflow:hidden;text-align:left;direction:ltr} .mp-banner-header{position:relative;height:36px;line-height:36px} .mp-banner-images{display:block;border:0} .mp-banner-pager{position:relative;width:980px;margin:0 auto;font-size:0} .mp-banner-pager .arrow-left,.mp-banner-pager .arrow-right{display:block;width:65px;height:65px;background:url(../images/banner-arrow.png) no-repeat} .mp-banner-pager-next,.mp-banner-pager-prev{position:absolute;top:-233px;width:65px;height:65px;cursor:pointer;z-index:10; display:none;} .mp-banner-pager-prev{left:0} .mp-banner-pager-prev .arrow-left{background-position:0 -225px} .mp-banner-pager-next{right:223px} .mp-banner-pager-next .arrow-right{background-position:0 -160px} .hover .mp-banner-pager-next,.hover .mp-banner-pager-prev{visibility:visible} .mp-banner-name{padding-left:10px;font-size:16px;color:#333} .mp-banner-type{position:absolute;top:0;right:0;height:36px;line-height:36px;font-size:13px;color:#6c6c6c;padding-right:.75em} .mp-banner-footer{height:35px;line-height:35px;font-size:13px;color:#6c6c6c;width:237px} .mp-banner-footer-center{color:#000;text-align:center} .mp-banner-footer-center em{font-style:normal;color:red} .mp-banner-bg{position:absolute;top:0;left:0;z-index:-2;height:100%;width:100%} .mp-banner-bg ul{width:100%;height:100%} .mp-banner-bg .list-item{display:block;height:100%} .mp-banner-bg .yeyou{background:url(../images/banner-bg-red.jpg) repeat} .mp-banner-bg .wangyou{background:url(../images/banner-bg-blue.jpg) repeat} .mp-banner-bg .shouyou{background:url(../images/banner-bg-purple.jpg) repeat} .mp-banner-bg .xiaoyouxi{background:url(../images/banner-bg-black.jpg) repeat} .mp-banner-footer2{position:relative;height:57px;padding-left:42px} .mp-banner-footer2 .num{position:absolute;top:0;left:0;font-size:52px;color:#fff} .mp-banner-footer2 h3{padding-top:5px;font-size:18px;height:28px;line-height:28px;color:#fff} .mp-banner-footer2 .platform,.mp-banner-footer2 .role{height:16px;line-height:16px;font-size:14px} .mp-banner-footer2 .role{color:#ff7a01;margin-right:6px} .mp-banner-footer2 .platform{color:#ccc} .mp-banner-intro{height:16px;line-height:16px;font-size:14px;color:#fff} .mp-banner-aside{position:absolute;top:20px;right:0;z-index:10;width:200px} .mp-banner-aside .title{position:relative;height:36px;line-height:36px;color:#fff;font-size:16px;text-align:center;background-image:-webkit-gradient(linear,left top,left bottom,from(#666),to(#4f4f4f));background-image:-webkit-linear-gradient(top,#666,#4f4f4f);background-image:-moz-linear-gradient(top,#666,#4f4f4f);background-image:-o-linear-gradient(top,#666,#4f4f4f);background-image:linear-gradient(to bottom,#666,#4f4f4f);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.Gradient(startColorstr='#ff666666',endColorstr='#ff4f4f4f',GradientType=0)} .mp-banner-aside .hot{position:absolute;top:0;left:0} .mp-banner-aside .list-item{position:relative;height:82px;background:#323232;vertical-align:middle} .mp-banner-aside a{display:block;height:100%} .mp-banner-aside a:hover{background:#3d3d3d; text-decoration:none;} .mp-banner-aside .pic{position:absolute;top:9px;left:9px;width:76px;height:64px} .mp-banner-aside .txt{padding-left:95px} .mp-banner-aside .txt .name,.mp-banner-aside .txt .platform,.mp-banner-aside .txt .role{height:22px;line-height:22px} .mp-banner-aside .txt .name{padding-top:9px;color:#fff;font-size:14px} .mp-banner-aside .txt .platform{color:#ee7116;font-size:13px} .mp-banner-aside .txt .role{color:#999;font-size:13px} ================================================ FILE: demo/javascript-seamless-handover/slide/js/startmove.js ================================================ function startMove(obj, json, fnEnd) { if (obj.timer) { clearInterval(obj.timer); } obj.timer = setInterval(function() { doMove(obj, json, fnEnd); }, 10); var oDate = new Date(); if (oDate.getTime() - obj.lastMove > 30) { doMove(obj, json, fnEnd); } } function getStyle(obj, attr){ return obj.currentStyle?obj.currentStyle[attr]:getComputedStyle(obj, false)[attr]; } function doMove(obj, json, fnEnd) { var iCur = 0; var attr = ''; var bStop = true; //假设运动已经该停止了 for (attr in json) { iCur = attr == 'opacity' ? parseInt(100 * parseFloat(getStyle(obj, 'opacity'))) : parseInt(getStyle(obj, attr)); if (isNaN(iCur)) { iCur = 0; } var iSpeed = (json[attr] - iCur) / 8; iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed); if (parseInt(json[attr]) != iCur) { bStop = false; } if (attr == 'opacity') { obj.style.filter = "alpha(opacity:" + (iCur + iSpeed) + ")"; obj.style.opacity = (iCur + iSpeed) / 100; } else { obj.style[attr] = iCur + iSpeed + 'px'; } } if (bStop) { clearInterval(obj.timer); obj.timer = null; if (fnEnd) { fnEnd(); } } obj.lastMove = (new Date()).getTime(); } ================================================ FILE: demo/js-template/demo.html ================================================ Js模板引擎

      {title}

      {content}

      ================================================ FILE: demo/mobile/position/absolute.html ================================================  绝对定位布局
      header

      君子曰:学不可以已。

      青,取之于蓝,而青于蓝;冰,水为之,而寒于水。木直中绳,輮以为轮,其曲中规。虽有槁暴(pù),不复挺者,輮使之然也。故木受绳则直,金就砺则利,君子博学而日参省乎己,则知明而行无过矣。

      故不登高山,不知天之高也;不临深溪,不知地之厚也;不闻先王之遗言,不知学问之大也。干、越、夷、貉之子,生而同声,长而异俗,教使之然也。诗曰:“嗟尔君子,无恒安息。靖共尔位,好是正直。神之听之,介尔景福。”神莫大于化道,福莫长于无祸。

      吾尝终日而思矣,不如须臾之所学也;吾尝跂而望矣,不如登高之博见也。登高而招,臂非加长也,而见者远;顺风而呼,声非加疾也,而闻者彰。假舆马者,非利足也,而致千里;假舟楫者,非能水也,而绝江河。君子生非异也,善假于物也。

      君子曰:学不可以已。

      青,取之于蓝,而青于蓝;冰,水为之,而寒于水。木直中绳,輮以为轮,其曲中规。虽有槁暴(pù),不复挺者,輮使之然也。故木受绳则直,金就砺则利,君子博学而日参省乎己,则知明而行无过矣。

      故不登高山,不知天之高也;不临深溪,不知地之厚也;不闻先王之遗言,不知学问之大也。干、越、夷、貉之子,生而同声,长而异俗,教使之然也。诗曰:“嗟尔君子,无恒安息。靖共尔位,好是正直。神之听之,介尔景福。”神莫大于化道,福莫长于无祸。

      吾尝终日而思矣,不如须臾之所学也;吾尝跂而望矣,不如登高之博见也。登高而招,臂非加长也,而见者远;顺风而呼,声非加疾也,而闻者彰。假舆马者,非利足也,而致千里;假舟楫者,非能水也,而绝江河。君子生非异也,善假于物也。

      君子曰:学不可以已。

      青,取之于蓝,而青于蓝;冰,水为之,而寒于水。木直中绳,輮以为轮,其曲中规。虽有槁暴(pù),不复挺者,輮使之然也。故木受绳则直,金就砺则利,君子博学而日参省乎己,则知明而行无过矣。

      故不登高山,不知天之高也;不临深溪,不知地之厚也;不闻先王之遗言,不知学问之大也。干、越、夷、貉之子,生而同声,长而异俗,教使之然也。诗曰:“嗟尔君子,无恒安息。靖共尔位,好是正直。神之听之,介尔景福。”神莫大于化道,福莫长于无祸。

      吾尝终日而思矣,不如须臾之所学也;吾尝跂而望矣,不如登高之博见也。登高而招,臂非加长也,而见者远;顺风而呼,声非加疾也,而闻者彰。假舆马者,非利足也,而致千里;假舟楫者,非能水也,而绝江河。君子生非异也,善假于物也。

      footer
      ================================================ FILE: demo/mobile/position/flex.html ================================================  绝对定位布局
      header

      君子曰:学不可以已。

      青,取之于蓝,而青于蓝;冰,水为之,而寒于水。木直中绳,輮以为轮,其曲中规。虽有槁暴(pù),不复挺者,輮使之然也。故木受绳则直,金就砺则利,君子博学而日参省乎己,则知明而行无过矣。

      故不登高山,不知天之高也;不临深溪,不知地之厚也;不闻先王之遗言,不知学问之大也。干、越、夷、貉之子,生而同声,长而异俗,教使之然也。诗曰:“嗟尔君子,无恒安息。靖共尔位,好是正直。神之听之,介尔景福。”神莫大于化道,福莫长于无祸。

      吾尝终日而思矣,不如须臾之所学也;吾尝跂而望矣,不如登高之博见也。登高而招,臂非加长也,而见者远;顺风而呼,声非加疾也,而闻者彰。假舆马者,非利足也,而致千里;假舟楫者,非能水也,而绝江河。君子生非异也,善假于物也。

      君子曰:学不可以已。

      青,取之于蓝,而青于蓝;冰,水为之,而寒于水。木直中绳,輮以为轮,其曲中规。虽有槁暴(pù),不复挺者,輮使之然也。故木受绳则直,金就砺则利,君子博学而日参省乎己,则知明而行无过矣。

      故不登高山,不知天之高也;不临深溪,不知地之厚也;不闻先王之遗言,不知学问之大也。干、越、夷、貉之子,生而同声,长而异俗,教使之然也。诗曰:“嗟尔君子,无恒安息。靖共尔位,好是正直。神之听之,介尔景福。”神莫大于化道,福莫长于无祸。

      吾尝终日而思矣,不如须臾之所学也;吾尝跂而望矣,不如登高之博见也。登高而招,臂非加长也,而见者远;顺风而呼,声非加疾也,而闻者彰。假舆马者,非利足也,而致千里;假舟楫者,非能水也,而绝江河。君子生非异也,善假于物也。

      君子曰:学不可以已。

      青,取之于蓝,而青于蓝;冰,水为之,而寒于水。木直中绳,輮以为轮,其曲中规。虽有槁暴(pù),不复挺者,輮使之然也。故木受绳则直,金就砺则利,君子博学而日参省乎己,则知明而行无过矣。

      故不登高山,不知天之高也;不临深溪,不知地之厚也;不闻先王之遗言,不知学问之大也。干、越、夷、貉之子,生而同声,长而异俗,教使之然也。诗曰:“嗟尔君子,无恒安息。靖共尔位,好是正直。神之听之,介尔景福。”神莫大于化道,福莫长于无祸。

      吾尝终日而思矣,不如须臾之所学也;吾尝跂而望矣,不如登高之博见也。登高而招,臂非加长也,而见者远;顺风而呼,声非加疾也,而闻者彰。假舆马者,非利足也,而致千里;假舟楫者,非能水也,而绝江河。君子生非异也,善假于物也。

      footer
      ================================================ FILE: demo/mobile/position/reset.css ================================================ .clearfix:before,.clearfix:after{content:"";display:table}.clearfix:after{clear:both}html,body{height:100%}html{font-family:"Helvetica Neue",Helvetica,STHeiTi,Arial,sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;font-size:62.5%}body{margin:0;font-size:1.4rem;line-height:1.5;color:#333;background-color:#fff}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:transparent;text-decoration:none;-webkit-tap-highlight-color:transparent;color:#08c}a:active{outline:0}a:active{color:#069}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0;vertical-align:middle}svg:not(:root){overflow:hidden}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto;white-space:pre;white-space:pre-wrap;word-wrap:break-word}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto;resize:vertical}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}html,button,input,select,textarea{font-family:"Helvetica Neue",Helvetica,STHeiTi,Arial,sans-serif}h1,h2,h3,h4,h5,h6,p,figure,form,blockquote{margin:0}ul,ol,li,dl,dd{margin:0;padding:0}ul,ol{list-style:none outside none}h1,h2,h3{line-height:2;font-weight:normal}h1{font-size:1.8rem}h2{font-size:1.6rem}h3{font-size:1.4rem}input::-moz-placeholder,textarea::-moz-placeholder{color:#ccc}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#ccc}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#ccc}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box} ================================================ FILE: demo/my-show-4-angularjs/index.html ================================================ 
      ================================================ FILE: demo/my-show-4-angularjs/index.js ================================================  //将路由注入进来 var projectShowApp= angular.module('project-show', ['ngRoute']); //配置路由 projectShowApp.config(function($routeProvider) { $routeProvider .when('/', { templateUrl : 'pages/home.html', controller : 'mainController' }) .when('/roadsideAssistance', { templateUrl : 'pages/project.html', controller : 'roadsideAssistanceController' }) .when('/westom', { templateUrl : 'pages/project.html', controller : 'westomController' }); }); projectShowApp.controller('mainController', function($scope) { $scope.message = { name : '王兴龙', mail : 'hacke2@qq.com', other : '以下填写个人介绍' }; }); projectShowApp.controller('roadsideAssistanceController', function($scope) { $scope.project = { name : '道路救援', other : '以下为其他信息' }; }); projectShowApp.controller('westomController', function($scope) { $scope.project = { name : '威士顿智能家居', other : '以下为其他信息' }; }); ================================================ FILE: demo/my-show-4-angularjs/js/angular-1.3.0.js ================================================ /** * @license AngularJS v1.3.0-beta.10 * (c) 2010-2014 Google, Inc. http://angularjs.org * License: MIT */ (function(window, document, undefined) {'use strict'; /** * @description * * This object provides a utility for producing rich Error messages within * Angular. It can be called as follows: * * var exampleMinErr = minErr('example'); * throw exampleMinErr('one', 'This {0} is {1}', foo, bar); * * The above creates an instance of minErr in the example namespace. The * resulting error will have a namespaced error code of example.one. The * resulting error will replace {0} with the value of foo, and {1} with the * value of bar. The object is not restricted in the number of arguments it can * take. * * If fewer arguments are specified than necessary for interpolation, the extra * interpolation markers will be preserved in the final string. * * Since data will be parsed statically during a build step, some restrictions * are applied with respect to how minErr instances are created and called. * Instances should have names of the form namespaceMinErr for a minErr created * using minErr('namespace') . Error codes, namespaces and template strings * should all be static strings, not variables or general expressions. * * @param {string} module The namespace to use for the new minErr instance. * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance */ function minErr(module) { return function () { var code = arguments[0], prefix = '[' + (module ? module + ':' : '') + code + '] ', template = arguments[1], templateArgs = arguments, stringify = function (obj) { if (typeof obj === 'function') { return obj.toString().replace(/ \{[\s\S]*$/, ''); } else if (typeof obj === 'undefined') { return 'undefined'; } else if (typeof obj !== 'string') { return JSON.stringify(obj); } return obj; }, message, i; message = prefix + template.replace(/\{\d+\}/g, function (match) { var index = +match.slice(1, -1), arg; if (index + 2 < templateArgs.length) { arg = templateArgs[index + 2]; if (typeof arg === 'function') { return arg.toString().replace(/ ?\{[\s\S]*$/, ''); } else if (typeof arg === 'undefined') { return 'undefined'; } else if (typeof arg !== 'string') { return toJson(arg); } return arg; } return match; }); message = message + '\nhttp://errors.angularjs.org/1.3.0-beta.10/' + (module ? module + '/' : '') + code; for (i = 2; i < arguments.length; i++) { message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' + encodeURIComponent(stringify(arguments[i])); } return new Error(message); }; } /* We need to tell jshint what variables are being exported */ /* global -angular, -msie, -jqLite, -jQuery, -slice, -push, -toString, -ngMinErr, -angularModule, -nodeName_, -uid, -lowercase, -uppercase, -manualLowercase, -manualUppercase, -nodeName_, -isArrayLike, -forEach, -sortedKeys, -forEachSorted, -reverseParams, -nextUid, -setHashKey, -extend, -int, -inherit, -noop, -identity, -valueFn, -isUndefined, -isDefined, -isObject, -isString, -isNumber, -isDate, -isArray, -isFunction, -isRegExp, -isWindow, -isScope, -isFile, -isBlob, -isBoolean, -trim, -isElement, -makeMap, -map, -size, -includes, -indexOf, -arrayRemove, -isLeafNode, -copy, -shallowCopy, -equals, -csp, -concat, -sliceArgs, -bind, -toJsonReplacer, -toJson, -fromJson, -toBoolean, -startingTag, -tryDecodeURIComponent, -parseKeyValue, -toKeyValue, -encodeUriSegment, -encodeUriQuery, -angularInit, -bootstrap, -snake_case, -bindJQuery, -assertArg, -assertArgFn, -assertNotHasOwnProperty, -getter, -getBlockElements, -hasOwnProperty, */ //////////////////////////////////// /** * @ngdoc module * @name ng * @module ng * @description * * # ng (core module) * The ng module is loaded by default when an AngularJS application is started. The module itself * contains the essential components for an AngularJS application to function. The table below * lists a high level breakdown of each of the services/factories, filters, directives and testing * components available within this core module. * *
      */ /** * @ngdoc function * @name angular.lowercase * @module ng * @function * * @description Converts the specified string to lowercase. * @param {string} string String to be converted to lowercase. * @returns {string} Lowercased string. */ var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;}; var hasOwnProperty = Object.prototype.hasOwnProperty; /** * @ngdoc function * @name angular.uppercase * @module ng * @function * * @description Converts the specified string to uppercase. * @param {string} string String to be converted to uppercase. * @returns {string} Uppercased string. */ var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;}; var manualLowercase = function(s) { /* jshint bitwise: false */ return isString(s) ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);}) : s; }; var manualUppercase = function(s) { /* jshint bitwise: false */ return isString(s) ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);}) : s; }; // String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish // locale, for this reason we need to detect this case and redefine lowercase/uppercase methods // with correct but slower alternatives. if ('i' !== 'I'.toLowerCase()) { lowercase = manualLowercase; uppercase = manualUppercase; } var /** holds major version number for IE or NaN for real browsers */ msie, jqLite, // delay binding since jQuery could be loaded after us. jQuery, // delay binding slice = [].slice, push = [].push, toString = Object.prototype.toString, ngMinErr = minErr('ng'), /** @name angular */ angular = window.angular || (window.angular = {}), angularModule, nodeName_, uid = ['0', '0', '0']; /** * IE 11 changed the format of the UserAgent string. * See http://msdn.microsoft.com/en-us/library/ms537503.aspx */ msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); if (isNaN(msie)) { msie = int((/trident\/.*; rv:(\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); } /** * @private * @param {*} obj * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, * String ...) */ function isArrayLike(obj) { if (obj == null || isWindow(obj)) { return false; } var length = obj.length; if (obj.nodeType === 1 && length) { return true; } return isString(obj) || isArray(obj) || length === 0 || typeof length === 'number' && length > 0 && (length - 1) in obj; } /** * @ngdoc function * @name angular.forEach * @module ng * @function * * @description * Invokes the `iterator` function once for each item in `obj` collection, which can be either an * object or an array. The `iterator` function is invoked with `iterator(value, key)`, where `value` * is the value of an object property or an array element and `key` is the object property key or * array element index. Specifying a `context` for the function is optional. * * It is worth noting that `.forEach` does not iterate over inherited properties because it filters * using the `hasOwnProperty` method. * ```js var values = {name: 'misko', gender: 'male'}; var log = []; angular.forEach(values, function(value, key) { this.push(key + ': ' + value); }, log); expect(log).toEqual(['name: misko', 'gender: male']); ``` * * @param {Object|Array} obj Object to iterate over. * @param {Function} iterator Iterator function. * @param {Object=} context Object to become context (`this`) for the iterator function. * @returns {Object|Array} Reference to `obj`. */ function forEach(obj, iterator, context) { var key; if (obj) { if (isFunction(obj)) { for (key in obj) { // Need to check if hasOwnProperty exists, // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { iterator.call(context, obj[key], key); } } } else if (obj.forEach && obj.forEach !== forEach) { obj.forEach(iterator, context); } else if (isArrayLike(obj)) { for (key = 0; key < obj.length; key++) iterator.call(context, obj[key], key); } else { for (key in obj) { if (obj.hasOwnProperty(key)) { iterator.call(context, obj[key], key); } } } } return obj; } function sortedKeys(obj) { var keys = []; for (var key in obj) { if (obj.hasOwnProperty(key)) { keys.push(key); } } return keys.sort(); } function forEachSorted(obj, iterator, context) { var keys = sortedKeys(obj); for ( var i = 0; i < keys.length; i++) { iterator.call(context, obj[keys[i]], keys[i]); } return keys; } /** * when using forEach the params are value, key, but it is often useful to have key, value. * @param {function(string, *)} iteratorFn * @returns {function(*, string)} */ function reverseParams(iteratorFn) { return function(value, key) { iteratorFn(key, value); }; } /** * A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric * characters such as '012ABC'. The reason why we are not using simply a number counter is that * the number string gets longer over time, and it can also overflow, where as the nextId * will grow much slower, it is a string, and it will never overflow. * * @returns {string} an unique alpha-numeric string */ function nextUid() { var index = uid.length; var digit; while(index) { index--; digit = uid[index].charCodeAt(0); if (digit == 57 /*'9'*/) { uid[index] = 'A'; return uid.join(''); } if (digit == 90 /*'Z'*/) { uid[index] = '0'; } else { uid[index] = String.fromCharCode(digit + 1); return uid.join(''); } } uid.unshift('0'); return uid.join(''); } /** * Set or clear the hashkey for an object. * @param obj object * @param h the hashkey (!truthy to delete the hashkey) */ function setHashKey(obj, h) { if (h) { obj.$$hashKey = h; } else { delete obj.$$hashKey; } } /** * @ngdoc function * @name angular.extend * @module ng * @function * * @description * Extends the destination object `dst` by copying all of the properties from the `src` object(s) * to `dst`. You can specify multiple `src` objects. * * @param {Object} dst Destination object. * @param {...Object} src Source object(s). * @returns {Object} Reference to `dst`. */ function extend(dst) { var h = dst.$$hashKey; forEach(arguments, function(obj) { if (obj !== dst) { forEach(obj, function(value, key) { dst[key] = value; }); } }); setHashKey(dst,h); return dst; } function int(str) { return parseInt(str, 10); } function inherit(parent, extra) { return extend(new (extend(function() {}, {prototype:parent}))(), extra); } /** * @ngdoc function * @name angular.noop * @module ng * @function * * @description * A function that performs no operations. This function can be useful when writing code in the * functional style. ```js function foo(callback) { var result = calculateResult(); (callback || angular.noop)(result); } ``` */ function noop() {} noop.$inject = []; /** * @ngdoc function * @name angular.identity * @module ng * @function * * @description * A function that returns its first argument. This function is useful when writing code in the * functional style. * ```js function transformer(transformationFn, value) { return (transformationFn || angular.identity)(value); }; ``` */ function identity($) {return $;} identity.$inject = []; function valueFn(value) {return function() {return value;};} /** * @ngdoc function * @name angular.isUndefined * @module ng * @function * * @description * Determines if a reference is undefined. * * @param {*} value Reference to check. * @returns {boolean} True if `value` is undefined. */ function isUndefined(value){return typeof value === 'undefined';} /** * @ngdoc function * @name angular.isDefined * @module ng * @function * * @description * Determines if a reference is defined. * * @param {*} value Reference to check. * @returns {boolean} True if `value` is defined. */ function isDefined(value){return typeof value !== 'undefined';} /** * @ngdoc function * @name angular.isObject * @module ng * @function * * @description * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not * considered to be objects. Note that JavaScript arrays are objects. * * @param {*} value Reference to check. * @returns {boolean} True if `value` is an `Object` but not `null`. */ function isObject(value){return value != null && typeof value === 'object';} /** * @ngdoc function * @name angular.isString * @module ng * @function * * @description * Determines if a reference is a `String`. * * @param {*} value Reference to check. * @returns {boolean} True if `value` is a `String`. */ function isString(value){return typeof value === 'string';} /** * @ngdoc function * @name angular.isNumber * @module ng * @function * * @description * Determines if a reference is a `Number`. * * @param {*} value Reference to check. * @returns {boolean} True if `value` is a `Number`. */ function isNumber(value){return typeof value === 'number';} /** * @ngdoc function * @name angular.isDate * @module ng * @function * * @description * Determines if a value is a date. * * @param {*} value Reference to check. * @returns {boolean} True if `value` is a `Date`. */ function isDate(value) { return toString.call(value) === '[object Date]'; } /** * @ngdoc function * @name angular.isArray * @module ng * @function * * @description * Determines if a reference is an `Array`. * * @param {*} value Reference to check. * @returns {boolean} True if `value` is an `Array`. */ function isArray(value) { return toString.call(value) === '[object Array]'; } /** * @ngdoc function * @name angular.isFunction * @module ng * @function * * @description * Determines if a reference is a `Function`. * * @param {*} value Reference to check. * @returns {boolean} True if `value` is a `Function`. */ function isFunction(value){return typeof value === 'function';} /** * Determines if a value is a regular expression object. * * @private * @param {*} value Reference to check. * @returns {boolean} True if `value` is a `RegExp`. */ function isRegExp(value) { return toString.call(value) === '[object RegExp]'; } /** * Checks if `obj` is a window object. * * @private * @param {*} obj Object to check * @returns {boolean} True if `obj` is a window obj. */ function isWindow(obj) { return obj && obj.document && obj.location && obj.alert && obj.setInterval; } function isScope(obj) { return obj && obj.$evalAsync && obj.$watch; } function isFile(obj) { return toString.call(obj) === '[object File]'; } function isBlob(obj) { return toString.call(obj) === '[object Blob]'; } function isBoolean(value) { return typeof value === 'boolean'; } var trim = (function() { // native trim is way faster: http://jsperf.com/angular-trim-test // but IE doesn't have it... :-( // TODO: we should move this into IE/ES5 polyfill if (!String.prototype.trim) { return function(value) { return isString(value) ? value.replace(/^\s\s*/, '').replace(/\s\s*$/, '') : value; }; } return function(value) { return isString(value) ? value.trim() : value; }; })(); /** * @ngdoc function * @name angular.isElement * @module ng * @function * * @description * Determines if a reference is a DOM element (or wrapped jQuery element). * * @param {*} value Reference to check. * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). */ function isElement(node) { return !!(node && (node.nodeName // we are a direct element || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API } /** * @param str 'key1,key2,...' * @returns {object} in the form of {key1:true, key2:true, ...} */ function makeMap(str) { var obj = {}, items = str.split(","), i; for ( i = 0; i < items.length; i++ ) obj[ items[i] ] = true; return obj; } if (msie < 9) { nodeName_ = function(element) { element = element.nodeName ? element : element[0]; return (element.scopeName && element.scopeName != 'HTML') ? uppercase(element.scopeName + ':' + element.nodeName) : element.nodeName; }; } else { nodeName_ = function(element) { return element.nodeName ? element.nodeName : element[0].nodeName; }; } function map(obj, iterator, context) { var results = []; forEach(obj, function(value, index, list) { results.push(iterator.call(context, value, index, list)); }); return results; } /** * @description * Determines the number of elements in an array, the number of properties an object has, or * the length of a string. * * Note: This function is used to augment the Object type in Angular expressions. See * {@link angular.Object} for more information about Angular arrays. * * @param {Object|Array|string} obj Object, array, or string to inspect. * @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object * @returns {number} The size of `obj` or `0` if `obj` is neither an object nor an array. */ function size(obj, ownPropsOnly) { var count = 0, key; if (isArray(obj) || isString(obj)) { return obj.length; } else if (isObject(obj)) { for (key in obj) if (!ownPropsOnly || obj.hasOwnProperty(key)) count++; } return count; } function includes(array, obj) { return indexOf(array, obj) != -1; } function indexOf(array, obj) { if (array.indexOf) return array.indexOf(obj); for (var i = 0; i < array.length; i++) { if (obj === array[i]) return i; } return -1; } function arrayRemove(array, value) { var index = indexOf(array, value); if (index >=0) array.splice(index, 1); return value; } function isLeafNode (node) { if (node) { switch (node.nodeName) { case "OPTION": case "PRE": case "TITLE": return true; } } return false; } /** * @ngdoc function * @name angular.copy * @module ng * @function * * @description * Creates a deep copy of `source`, which should be an object or an array. * * * If no destination is supplied, a copy of the object or array is created. * * If a destination is provided, all of its elements (for array) or properties (for objects) * are deleted and then all elements/properties from the source are copied to it. * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. * * If `source` is identical to 'destination' an exception will be thrown. * * @param {*} source The source that will be used to make a copy. * Can be any type, including primitives, `null`, and `undefined`. * @param {(Object|Array)=} destination Destination into which the source is copied. If * provided, must be of the same type as `source`. * @returns {*} The copy or updated `destination`, if `destination` was specified. * * @example
      Name:
      E-mail:
      Gender: male female
      form = {{user | json}}
      master = {{master | json}}
      */ function copy(source, destination) { if (isWindow(source) || isScope(source)) { throw ngMinErr('cpws', "Can't copy! Making copies of Window or Scope instances is not supported."); } if (!destination) { destination = source; if (source) { if (isArray(source)) { destination = copy(source, []); } else if (isDate(source)) { destination = new Date(source.getTime()); } else if (isRegExp(source)) { destination = new RegExp(source.source); } else if (isObject(source)) { destination = copy(source, {}); } } } else { if (source === destination) throw ngMinErr('cpi', "Can't copy! Source and destination are identical."); if (isArray(source)) { destination.length = 0; for ( var i = 0; i < source.length; i++) { destination.push(copy(source[i])); } } else { var h = destination.$$hashKey; forEach(destination, function(value, key) { delete destination[key]; }); for ( var key in source) { destination[key] = copy(source[key]); } setHashKey(destination,h); } } return destination; } /** * Create a shallow copy of an object */ function shallowCopy(src, dst) { dst = dst || {}; for(var key in src) { // shallowCopy is only ever called by $compile nodeLinkFn, which has control over src // so we don't need to worry about using our custom hasOwnProperty here if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { dst[key] = src[key]; } } return dst; } /** * @ngdoc function * @name angular.equals * @module ng * @function * * @description * Determines if two objects or two values are equivalent. Supports value types, regular * expressions, arrays and objects. * * Two objects or values are considered equivalent if at least one of the following is true: * * * Both objects or values pass `===` comparison. * * Both objects or values are of the same type and all of their properties are equal by * comparing them with `angular.equals`. * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) * * Both values represent the same regular expression (In JavaScript, * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual * representation matches). * * During a property comparison, properties of `function` type and properties with names * that begin with `$` are ignored. * * Scope and DOMWindow objects are being compared only by identify (`===`). * * @param {*} o1 Object or value to compare. * @param {*} o2 Object or value to compare. * @returns {boolean} True if arguments are equal. */ function equals(o1, o2) { if (o1 === o2) return true; if (o1 === null || o2 === null) return false; if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN var t1 = typeof o1, t2 = typeof o2, length, key, keySet; if (t1 == t2) { if (t1 == 'object') { if (isArray(o1)) { if (!isArray(o2)) return false; if ((length = o1.length) == o2.length) { for(key=0; key 2 ? sliceArgs(arguments, 2) : []; if (isFunction(fn) && !(fn instanceof RegExp)) { return curryArgs.length ? function() { return arguments.length ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0))) : fn.apply(self, curryArgs); } : function() { return arguments.length ? fn.apply(self, arguments) : fn.call(self); }; } else { // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) return fn; } } function toJsonReplacer(key, value) { var val = value; if (typeof key === 'string' && key.charAt(0) === '$') { val = undefined; } else if (isWindow(value)) { val = '$WINDOW'; } else if (value && document === value) { val = '$DOCUMENT'; } else if (isScope(value)) { val = '$SCOPE'; } return val; } /** * @ngdoc function * @name angular.toJson * @module ng * @function * * @description * Serializes input into a JSON-formatted string. Properties with leading $ characters will be * stripped since angular uses this notation internally. * * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. * @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace. * @returns {string|undefined} JSON-ified string representing `obj`. */ function toJson(obj, pretty) { if (typeof obj === 'undefined') return undefined; return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null); } /** * @ngdoc function * @name angular.fromJson * @module ng * @function * * @description * Deserializes a JSON string. * * @param {string} json JSON string to deserialize. * @returns {Object|Array|string|number} Deserialized thingy. */ function fromJson(json) { return isString(json) ? JSON.parse(json) : json; } function toBoolean(value) { if (typeof value === 'function') { value = true; } else if (value && value.length !== 0) { var v = lowercase("" + value); value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]'); } else { value = false; } return value; } /** * @returns {string} Returns the string representation of the element. */ function startingTag(element) { element = jqLite(element).clone(); try { // turns out IE does not let you set .html() on elements which // are not allowed to have children. So we just ignore it. element.empty(); } catch(e) {} // As Per DOM Standards var TEXT_NODE = 3; var elemHtml = jqLite('
      ').append(element).html(); try { return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) : elemHtml. match(/^(<[^>]+>)/)[1]. replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); } catch(e) { return lowercase(elemHtml); } } ///////////////////////////////////////////////// /** * Tries to decode the URI component without throwing an exception. * * @private * @param str value potential URI component to check. * @returns {boolean} True if `value` can be decoded * with the decodeURIComponent function. */ function tryDecodeURIComponent(value) { try { return decodeURIComponent(value); } catch(e) { // Ignore any invalid uri component } } /** * Parses an escaped url query string into key-value pairs. * @returns {Object.} */ function parseKeyValue(/**string*/keyValue) { var obj = {}, key_value, key; forEach((keyValue || "").split('&'), function(keyValue) { if ( keyValue ) { key_value = keyValue.split('='); key = tryDecodeURIComponent(key_value[0]); if ( isDefined(key) ) { var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; if (!obj[key]) { obj[key] = val; } else if(isArray(obj[key])) { obj[key].push(val); } else { obj[key] = [obj[key],val]; } } } }); return obj; } function toKeyValue(obj) { var parts = []; forEach(obj, function(value, key) { if (isArray(value)) { forEach(value, function(arrayValue) { parts.push(encodeUriQuery(key, true) + (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); }); } else { parts.push(encodeUriQuery(key, true) + (value === true ? '' : '=' + encodeUriQuery(value, true))); } }); return parts.length ? parts.join('&') : ''; } /** * We need our custom method because encodeURIComponent is too aggressive and doesn't follow * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path * segments: * segment = *pchar * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" * pct-encoded = "%" HEXDIG HEXDIG * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" * / "*" / "+" / "," / ";" / "=" */ function encodeUriSegment(val) { return encodeUriQuery(val, true). replace(/%26/gi, '&'). replace(/%3D/gi, '='). replace(/%2B/gi, '+'); } /** * This method is intended for encoding *key* or *value* parts of query component. We need a custom * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be * encoded per http://tools.ietf.org/html/rfc3986: * query = *( pchar / "/" / "?" ) * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" * pct-encoded = "%" HEXDIG HEXDIG * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" * / "*" / "+" / "," / ";" / "=" */ function encodeUriQuery(val, pctEncodeSpaces) { return encodeURIComponent(val). replace(/%40/gi, '@'). replace(/%3A/gi, ':'). replace(/%24/g, '$'). replace(/%2C/gi, ','). replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); } var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-']; function getNgAttribute(element, ngAttr) { var attr, i, ii = ngAttrPrefixes.length, j, jj; element = jqLite(element); for (i=0; i` or `` tags. * * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp` * found in the document will be used to define the root element to auto-bootstrap as an * application. To run multiple applications in an HTML document you must manually bootstrap them using * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other. * * You can specify an **AngularJS module** to be used as the root module for the application. This * module will be loaded into the {@link auto.$injector} when the application is bootstrapped and * should contain the application code needed or have dependencies on other modules that will * contain the code. See {@link angular.module} for more information. * * In the example below if the `ngApp` directive were not placed on the `html` element then the * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` * would not be resolved to `3`. * * `ngApp` is the easiest, and most common, way to bootstrap an application. *
      I can add: {{a}} + {{b}} = {{ a+b }}
      angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { $scope.a = 1; $scope.b = 2; });
      * * Using `ngStrictDi`, you would see something like this: *
      I can add: {{a}} + {{b}} = {{ a+b }}

      This renders because the controller does not fail to instantiate, by using explicit annotation style (see script.js for details)

      Name:
      Hello, {{name}}!

      This renders because the controller does not fail to instantiate, by using explicit annotation style (see script.js for details)

      I can add: {{a}} + {{b}} = {{ a+b }}

      The controller could not be instantiated, due to relying on automatic function annotations (which are disabled in strict mode). As such, the content of this section is not interpolated, and there should be an error in your web console.

      angular.module('ngAppStrictDemo', []) // BadController will fail to instantiate, due to relying on automatic function annotation, // rather than an explicit annotation .controller('BadController', function($scope) { $scope.a = 1; $scope.b = 2; }) // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated, // due to using explicit annotations using the array style and $inject property, respectively. .controller('GoodController1', ['$scope', function($scope) { $scope.a = 1; $scope.b = 2; }]) .controller('GoodController2', GoodController2); function GoodController2($scope) { $scope.name = "World"; } GoodController2.$inject = ['$scope']; div[ng-controller] { margin-bottom: 1em; -webkit-border-radius: 4px; border-radius: 4px; border: 1px solid; padding: .5em; } div[ng-controller^=Good] { border-color: #d6e9c6; background-color: #dff0d8; color: #3c763d; } div[ng-controller^=Bad] { border-color: #ebccd1; background-color: #f2dede; color: #a94442; margin-bottom: 0; }
      */ function angularInit(element, bootstrap) { var elements = [element], appElement, module, config = {}, names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'], options = { 'boolean': ['strict-di'] }, NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/; function append(element) { element && elements.push(element); } forEach(names, function(name) { names[name] = true; append(document.getElementById(name)); name = name.replace(':', '\\:'); if (element.querySelectorAll) { forEach(element.querySelectorAll('.' + name), append); forEach(element.querySelectorAll('.' + name + '\\:'), append); forEach(element.querySelectorAll('[' + name + ']'), append); } }); forEach(elements, function(element) { if (!appElement) { var className = ' ' + element.className + ' '; var match = NG_APP_CLASS_REGEXP.exec(className); if (match) { appElement = element; module = (match[2] || '').replace(/\s+/g, ','); } else { forEach(element.attributes, function(attr) { if (!appElement && names[attr.name]) { appElement = element; module = attr.value; } }); } } }); if (appElement) { config.strictDi = getNgAttribute(appElement, "strict-di") !== null; bootstrap(appElement, module ? [module] : [], config); } } /** * @ngdoc function * @name angular.bootstrap * @module ng * @description * Use this function to manually start up angular application. * * See: {@link guide/bootstrap Bootstrap} * * Note that Protractor based end-to-end tests cannot use this function to bootstrap manually. * They must use {@link ng.directive:ngApp ngApp}. * * Angular will detect if it has been loaded into the browser more than once and only allow the * first loaded script to be bootstrapped and will report a warning to the browser console for * each of the subsequent scripts. This prevents strange results in applications, where otherwise * multiple instances of Angular try to work on the DOM. * * ```html * * * *
      * {{greeting}} *
      * * * * * * ``` * * @param {DOMElement} element DOM element which is the root of angular application. * @param {Array=} modules an array of modules to load into the application. * Each item in the array should be the name of a predefined module or a (DI annotated) * function that will be invoked by the injector as a run block. * See: {@link angular.module modules} * @param {Object=} config an object for defining configuration options for the application. The * following keys are supported: * * - `strictDi`: disable automatic function annotation for the application. This is meant to * assist in finding bugs which break minified code. * * @returns {auto.$injector} Returns the newly created injector for this app. */ function bootstrap(element, modules, config) { if (!isObject(config)) config = {}; var defaultConfig = { strictDi: false }; config = extend(defaultConfig, config); var doBootstrap = function() { element = jqLite(element); if (element.injector()) { var tag = (element[0] === document) ? 'document' : startingTag(element); throw ngMinErr('btstrpd', "App Already Bootstrapped with this Element '{0}'", tag); } modules = modules || []; modules.unshift(['$provide', function($provide) { $provide.value('$rootElement', element); }]); modules.unshift('ng'); var injector = createInjector(modules, config.strictDi); injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate', function(scope, element, compile, injector, animate) { scope.$apply(function() { element.data('$injector', injector); compile(element)(scope); }); }] ); return injector; }; var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { return doBootstrap(); } window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); angular.resumeBootstrap = function(extraModules) { forEach(extraModules, function(module) { modules.push(module); }); doBootstrap(); }; } var SNAKE_CASE_REGEXP = /[A-Z]/g; function snake_case(name, separator) { separator = separator || '_'; return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { return (pos ? separator : '') + letter.toLowerCase(); }); } function bindJQuery() { var originalCleanData; // bind to jQuery if present; jQuery = window.jQuery; // Use jQuery if it exists with proper functionality, otherwise default to us. // Angular 1.2+ requires jQuery 1.7.1+ for on()/off() support. if (jQuery && jQuery.fn.on) { jqLite = jQuery; extend(jQuery.fn, { scope: JQLitePrototype.scope, isolateScope: JQLitePrototype.isolateScope, controller: JQLitePrototype.controller, injector: JQLitePrototype.injector, inheritedData: JQLitePrototype.inheritedData }); originalCleanData = jQuery.cleanData; // Prevent double-proxying. originalCleanData = originalCleanData.$$original || originalCleanData; // All nodes removed from the DOM via various jQuery APIs like .remove() // are passed through jQuery.cleanData. Monkey-patch this method to fire // the $destroy event on all removed nodes. jQuery.cleanData = function(elems) { for (var i = 0, elem; (elem = elems[i]) != null; i++) { jQuery(elem).triggerHandler('$destroy'); } originalCleanData(elems); }; jQuery.cleanData.$$original = originalCleanData; } else { jqLite = JQLite; } angular.element = jqLite; } /** * throw error if the argument is falsy. */ function assertArg(arg, name, reason) { if (!arg) { throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); } return arg; } function assertArgFn(arg, name, acceptArrayAnnotation) { if (acceptArrayAnnotation && isArray(arg)) { arg = arg[arg.length - 1]; } assertArg(isFunction(arg), name, 'not a function, got ' + (arg && typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg)); return arg; } /** * throw error if the name given is hasOwnProperty * @param {String} name the name to test * @param {String} context the context in which the name is used, such as module or directive */ function assertNotHasOwnProperty(name, context) { if (name === 'hasOwnProperty') { throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); } } /** * Return the value accessible from the object by path. Any undefined traversals are ignored * @param {Object} obj starting object * @param {String} path path to traverse * @param {boolean} [bindFnToScope=true] * @returns {Object} value as accessible by path */ //TODO(misko): this function needs to be removed function getter(obj, path, bindFnToScope) { if (!path) return obj; var keys = path.split('.'); var key; var lastInstance = obj; var len = keys.length; for (var i = 0; i < len; i++) { key = keys[i]; if (obj) { obj = (lastInstance = obj)[key]; } } if (!bindFnToScope && isFunction(obj)) { return bind(lastInstance, obj); } return obj; } /** * Return the DOM siblings between the first and last node in the given array. * @param {Array} array like object * @returns {DOMElement} object containing the elements */ function getBlockElements(nodes) { var startNode = nodes[0], endNode = nodes[nodes.length - 1]; if (startNode === endNode) { return jqLite(startNode); } var element = startNode; var elements = [element]; do { element = element.nextSibling; if (!element) break; elements.push(element); } while (element !== endNode); return jqLite(elements); } /** * @ngdoc type * @name angular.Module * @module ng * @description * * Interface for configuring angular {@link angular.module modules}. */ function setupModuleLoader(window) { var $injectorMinErr = minErr('$injector'); var ngMinErr = minErr('ng'); function ensure(obj, name, factory) { return obj[name] || (obj[name] = factory()); } var angular = ensure(window, 'angular', Object); // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap angular.$$minErr = angular.$$minErr || minErr; return ensure(angular, 'module', function() { /** @type {Object.} */ var modules = {}; /** * @ngdoc function * @name angular.module * @module ng * @description * * The `angular.module` is a global place for creating, registering and retrieving Angular * modules. * All modules (angular core or 3rd party) that should be available to an application must be * registered using this mechanism. * * When passed two or more arguments, a new module is created. If passed only one argument, an * existing module (the name passed as the first argument to `module`) is retrieved. * * * # Module * * A module is a collection of services, directives, filters, and configuration information. * `angular.module` is used to configure the {@link auto.$injector $injector}. * * ```js * // Create a new module * var myModule = angular.module('myModule', []); * * // register a new service * myModule.value('appName', 'MyCoolApp'); * * // configure existing services inside initialization blocks. * myModule.config(['$locationProvider', function($locationProvider) { * // Configure existing providers * $locationProvider.hashPrefix('!'); * }]); * ``` * * Then you can create an injector and load your modules like this: * * ```js * var injector = angular.injector(['ng', 'myModule']) * ``` * * However it's more likely that you'll just use * {@link ng.directive:ngApp ngApp} or * {@link angular.bootstrap} to simplify this process for you. * * @param {!string} name The name of the module to create or retrieve. * @param {!Array.=} requires If specified then new module is being created. If * unspecified then the module is being retrieved for further configuration. * @param {Function=} configFn Optional configuration function for the module. Same as * {@link angular.Module#config Module#config()}. * @returns {module} new module with the {@link angular.Module} api. */ return function module(name, requires, configFn) { var assertNotHasOwnProperty = function(name, context) { if (name === 'hasOwnProperty') { throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); } }; assertNotHasOwnProperty(name, 'module'); if (requires && modules.hasOwnProperty(name)) { modules[name] = null; } return ensure(modules, name, function() { if (!requires) { throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + "the module name or forgot to load it. If registering a module ensure that you " + "specify the dependencies as the second argument.", name); } /** @type {!Array.>} */ var invokeQueue = []; /** @type {!Array.} */ var configBlocks = []; /** @type {!Array.} */ var runBlocks = []; var config = invokeLater('$injector', 'invoke', 'push', configBlocks); /** @type {angular.Module} */ var moduleInstance = { // Private state _invokeQueue: invokeQueue, _configBlocks: configBlocks, _runBlocks: runBlocks, /** * @ngdoc property * @name angular.Module#requires * @module ng * @returns {Array.} List of module names which must be loaded before this module. * @description * Holds the list of modules which the injector will load before the current module is * loaded. */ requires: requires, /** * @ngdoc property * @name angular.Module#name * @module ng * @returns {string} Name of the module. * @description */ name: name, /** * @ngdoc method * @name angular.Module#provider * @module ng * @param {string} name service name * @param {Function} providerType Construction function for creating new instance of the * service. * @description * See {@link auto.$provide#provider $provide.provider()}. */ provider: invokeLater('$provide', 'provider'), /** * @ngdoc method * @name angular.Module#factory * @module ng * @param {string} name service name * @param {Function} providerFunction Function for creating new instance of the service. * @description * See {@link auto.$provide#factory $provide.factory()}. */ factory: invokeLater('$provide', 'factory'), /** * @ngdoc method * @name angular.Module#service * @module ng * @param {string} name service name * @param {Function} constructor A constructor function that will be instantiated. * @description * See {@link auto.$provide#service $provide.service()}. */ service: invokeLater('$provide', 'service'), /** * @ngdoc method * @name angular.Module#value * @module ng * @param {string} name service name * @param {*} object Service instance object. * @description * See {@link auto.$provide#value $provide.value()}. */ value: invokeLater('$provide', 'value'), /** * @ngdoc method * @name angular.Module#constant * @module ng * @param {string} name constant name * @param {*} object Constant value. * @description * Because the constant are fixed, they get applied before other provide methods. * See {@link auto.$provide#constant $provide.constant()}. */ constant: invokeLater('$provide', 'constant', 'unshift'), /** * @ngdoc method * @name angular.Module#animation * @module ng * @param {string} name animation name * @param {Function} animationFactory Factory function for creating new instance of an * animation. * @description * * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. * * * Defines an animation hook that can be later used with * {@link ngAnimate.$animate $animate} service and directives that use this service. * * ```js * module.animation('.animation-name', function($inject1, $inject2) { * return { * eventName : function(element, done) { * //code to run the animation * //once complete, then run done() * return function cancellationFunction(element) { * //code to cancel the animation * } * } * } * }) * ``` * * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and * {@link ngAnimate ngAnimate module} for more information. */ animation: invokeLater('$animateProvider', 'register'), /** * @ngdoc method * @name angular.Module#filter * @module ng * @param {string} name Filter name. * @param {Function} filterFactory Factory function for creating new instance of filter. * @description * See {@link ng.$filterProvider#register $filterProvider.register()}. */ filter: invokeLater('$filterProvider', 'register'), /** * @ngdoc method * @name angular.Module#controller * @module ng * @param {string|Object} name Controller name, or an object map of controllers where the * keys are the names and the values are the constructors. * @param {Function} constructor Controller constructor function. * @description * See {@link ng.$controllerProvider#register $controllerProvider.register()}. */ controller: invokeLater('$controllerProvider', 'register'), /** * @ngdoc method * @name angular.Module#directive * @module ng * @param {string|Object} name Directive name, or an object map of directives where the * keys are the names and the values are the factories. * @param {Function} directiveFactory Factory function for creating new instance of * directives. * @description * See {@link ng.$compileProvider#directive $compileProvider.directive()}. */ directive: invokeLater('$compileProvider', 'directive'), /** * @ngdoc method * @name angular.Module#config * @module ng * @param {Function} configFn Execute this function on module load. Useful for service * configuration. * @description * Use this method to register work which needs to be performed on module loading. * For more about how to configure services, see * {@link providers#providers_provider-recipe Provider Recipe}. */ config: config, /** * @ngdoc method * @name angular.Module#run * @module ng * @param {Function} initializationFn Execute this function after injector creation. * Useful for application initialization. * @description * Use this method to register work which should be performed when the injector is done * loading all modules. */ run: function(block) { runBlocks.push(block); return this; } }; if (configFn) { config(configFn); } return moduleInstance; /** * @param {string} provider * @param {string} method * @param {String=} insertMethod * @returns {angular.Module} */ function invokeLater(provider, method, insertMethod, queue) { if (!queue) queue = invokeQueue; return function() { queue[insertMethod || 'push']([provider, method, arguments]); return moduleInstance; }; } }); }; }); } /* global angularModule: true, version: true, $LocaleProvider, $CompileProvider, htmlAnchorDirective, inputDirective, inputDirective, formDirective, scriptDirective, selectDirective, styleDirective, optionDirective, ngBindDirective, ngBindHtmlDirective, ngBindTemplateDirective, ngClassDirective, ngClassEvenDirective, ngClassOddDirective, ngCspDirective, ngCloakDirective, ngControllerDirective, ngFormDirective, ngHideDirective, ngIfDirective, ngIncludeDirective, ngIncludeFillContentDirective, ngInitDirective, ngNonBindableDirective, ngPluralizeDirective, ngRepeatDirective, ngShowDirective, ngStyleDirective, ngSwitchDirective, ngSwitchWhenDirective, ngSwitchDefaultDirective, ngOptionsDirective, ngTranscludeDirective, ngModelDirective, ngListDirective, ngChangeDirective, requiredDirective, requiredDirective, ngValueDirective, ngModelOptionsDirective, ngAttributeAliasDirectives, ngEventDirectives, $AnchorScrollProvider, $AnimateProvider, $BrowserProvider, $CacheFactoryProvider, $ControllerProvider, $DocumentProvider, $ExceptionHandlerProvider, $FilterProvider, $InterpolateProvider, $IntervalProvider, $HttpProvider, $HttpBackendProvider, $LocationProvider, $LogProvider, $ParseProvider, $RootScopeProvider, $QProvider, $$SanitizeUriProvider, $SceProvider, $SceDelegateProvider, $SnifferProvider, $TemplateCacheProvider, $TimeoutProvider, $$RAFProvider, $$AsyncCallbackProvider, $WindowProvider */ /** * @ngdoc object * @name angular.version * @module ng * @description * An object that contains information about the current AngularJS version. This object has the * following properties: * * - `full` – `{string}` – Full version string, such as "0.9.18". * - `major` – `{number}` – Major version number, such as "0". * - `minor` – `{number}` – Minor version number, such as "9". * - `dot` – `{number}` – Dot version number, such as "18". * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". */ var version = { full: '1.3.0-beta.10', // all of these placeholder strings will be replaced by grunt's major: 1, // package task minor: 3, dot: 0, codeName: 'excessive-clarification' }; function publishExternalAPI(angular){ extend(angular, { 'bootstrap': bootstrap, 'copy': copy, 'extend': extend, 'equals': equals, 'element': jqLite, 'forEach': forEach, 'injector': createInjector, 'noop':noop, 'bind':bind, 'toJson': toJson, 'fromJson': fromJson, 'identity':identity, 'isUndefined': isUndefined, 'isDefined': isDefined, 'isString': isString, 'isFunction': isFunction, 'isObject': isObject, 'isNumber': isNumber, 'isElement': isElement, 'isArray': isArray, 'version': version, 'isDate': isDate, 'lowercase': lowercase, 'uppercase': uppercase, 'callbacks': {counter: 0}, '$$minErr': minErr, '$$csp': csp }); angularModule = setupModuleLoader(window); try { angularModule('ngLocale'); } catch (e) { angularModule('ngLocale', []).provider('$locale', $LocaleProvider); } angularModule('ng', ['ngLocale'], ['$provide', function ngModule($provide) { // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. $provide.provider({ $$sanitizeUri: $$SanitizeUriProvider }); $provide.provider('$compile', $CompileProvider). directive({ a: htmlAnchorDirective, input: inputDirective, textarea: inputDirective, form: formDirective, script: scriptDirective, select: selectDirective, style: styleDirective, option: optionDirective, ngBind: ngBindDirective, ngBindHtml: ngBindHtmlDirective, ngBindTemplate: ngBindTemplateDirective, ngClass: ngClassDirective, ngClassEven: ngClassEvenDirective, ngClassOdd: ngClassOddDirective, ngCloak: ngCloakDirective, ngController: ngControllerDirective, ngForm: ngFormDirective, ngHide: ngHideDirective, ngIf: ngIfDirective, ngInclude: ngIncludeDirective, ngInit: ngInitDirective, ngNonBindable: ngNonBindableDirective, ngPluralize: ngPluralizeDirective, ngRepeat: ngRepeatDirective, ngShow: ngShowDirective, ngStyle: ngStyleDirective, ngSwitch: ngSwitchDirective, ngSwitchWhen: ngSwitchWhenDirective, ngSwitchDefault: ngSwitchDefaultDirective, ngOptions: ngOptionsDirective, ngTransclude: ngTranscludeDirective, ngModel: ngModelDirective, ngList: ngListDirective, ngChange: ngChangeDirective, required: requiredDirective, ngRequired: requiredDirective, ngValue: ngValueDirective, ngModelOptions: ngModelOptionsDirective }). directive({ ngInclude: ngIncludeFillContentDirective }). directive(ngAttributeAliasDirectives). directive(ngEventDirectives); $provide.provider({ $anchorScroll: $AnchorScrollProvider, $animate: $AnimateProvider, $browser: $BrowserProvider, $cacheFactory: $CacheFactoryProvider, $controller: $ControllerProvider, $document: $DocumentProvider, $exceptionHandler: $ExceptionHandlerProvider, $filter: $FilterProvider, $interpolate: $InterpolateProvider, $interval: $IntervalProvider, $http: $HttpProvider, $httpBackend: $HttpBackendProvider, $location: $LocationProvider, $log: $LogProvider, $parse: $ParseProvider, $rootScope: $RootScopeProvider, $q: $QProvider, $sce: $SceProvider, $sceDelegate: $SceDelegateProvider, $sniffer: $SnifferProvider, $templateCache: $TemplateCacheProvider, $timeout: $TimeoutProvider, $window: $WindowProvider, $$rAF: $$RAFProvider, $$asyncCallback : $$AsyncCallbackProvider }); } ]); } /* global -JQLitePrototype, -addEventListenerFn, -removeEventListenerFn, -BOOLEAN_ATTR */ ////////////////////////////////// //JQLite ////////////////////////////////// /** * @ngdoc function * @name angular.element * @module ng * @function * * @description * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. * * If jQuery is available, `angular.element` is an alias for the * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite." * *
      jqLite is a tiny, API-compatible subset of jQuery that allows * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most * commonly needed functionality with the goal of having a very small footprint.
      * * To use jQuery, simply load it before `DOMContentLoaded` event fired. * *
      **Note:** all element references in Angular are always wrapped with jQuery or * jqLite; they are never raw DOM references.
      * * ## Angular's jqLite * jqLite provides only the following jQuery methods: * * - [`addClass()`](http://api.jquery.com/addClass/) * - [`after()`](http://api.jquery.com/after/) * - [`append()`](http://api.jquery.com/append/) * - [`attr()`](http://api.jquery.com/attr/) * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData * - [`children()`](http://api.jquery.com/children/) - Does not support selectors * - [`clone()`](http://api.jquery.com/clone/) * - [`contents()`](http://api.jquery.com/contents/) * - [`css()`](http://api.jquery.com/css/) * - [`data()`](http://api.jquery.com/data/) * - [`empty()`](http://api.jquery.com/empty/) * - [`eq()`](http://api.jquery.com/eq/) * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name * - [`hasClass()`](http://api.jquery.com/hasClass/) * - [`html()`](http://api.jquery.com/html/) * - [`next()`](http://api.jquery.com/next/) - Does not support selectors * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors * - [`prepend()`](http://api.jquery.com/prepend/) * - [`prop()`](http://api.jquery.com/prop/) * - [`ready()`](http://api.jquery.com/ready/) * - [`remove()`](http://api.jquery.com/remove/) * - [`removeAttr()`](http://api.jquery.com/removeAttr/) * - [`removeClass()`](http://api.jquery.com/removeClass/) * - [`removeData()`](http://api.jquery.com/removeData/) * - [`replaceWith()`](http://api.jquery.com/replaceWith/) * - [`text()`](http://api.jquery.com/text/) * - [`toggleClass()`](http://api.jquery.com/toggleClass/) * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces * - [`val()`](http://api.jquery.com/val/) * - [`wrap()`](http://api.jquery.com/wrap/) * * ## jQuery/jqLite Extras * Angular also provides the following additional methods and events to both jQuery and jqLite: * * ### Events * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM * element before it is removed. * * ### Methods * - `controller(name)` - retrieves the controller of the current element or its parent. By default * retrieves controller associated with the `ngController` directive. If `name` is provided as * camelCase directive name, then the controller for this directive will be retrieved (e.g. * `'ngModel'`). * - `injector()` - retrieves the injector of the current element or its parent. * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current * element or its parent. * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the * current element. This getter should be used only on elements that contain a directive which starts a new isolate * scope. Calling `scope()` on this element always returns the original non-isolate scope. * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top * parent element is reached. * * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. * @returns {Object} jQuery object. */ var jqCache = JQLite.cache = {}, jqName = JQLite.expando = 'ng-' + new Date().getTime(), jqId = 1, addEventListenerFn = (window.document.addEventListener ? function(element, type, fn) {element.addEventListener(type, fn, false);} : function(element, type, fn) {element.attachEvent('on' + type, fn);}), removeEventListenerFn = (window.document.removeEventListener ? function(element, type, fn) {element.removeEventListener(type, fn, false); } : function(element, type, fn) {element.detachEvent('on' + type, fn); }); /* * !!! This is an undocumented "private" function !!! */ var jqData = JQLite._data = function(node) { //jQuery always returns an object on cache miss return this.cache[node[this.expando]] || {}; }; function jqNextId() { return ++jqId; } var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; var MOZ_HACK_REGEXP = /^moz([A-Z])/; var jqLiteMinErr = minErr('jqLite'); /** * Converts snake_case to camelCase. * Also there is special case for Moz prefix starting with upper case letter. * @param name Name to normalize */ function camelCase(name) { return name. replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { return offset ? letter.toUpperCase() : letter; }). replace(MOZ_HACK_REGEXP, 'Moz$1'); } var SINGLE_TAG_REGEXP = /^<(\w+)\s*\/?>(?:<\/\1>|)$/; var HTML_REGEXP = /<|&#?\w+;/; var TAG_NAME_REGEXP = /<([\w:]+)/; var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi; var wrapMap = { 'option': [1, ''], 'thead': [1, '', '
      '], 'col': [2, '', '
      '], 'tr': [2, '', '
      '], 'td': [3, '', '
      '], '_default': [0, "", ""] }; wrapMap.optgroup = wrapMap.option; wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; wrapMap.th = wrapMap.td; function jqLiteIsTextNode(html) { return !HTML_REGEXP.test(html); } function jqLiteBuildFragment(html, context) { var elem, tmp, tag, wrap, fragment = context.createDocumentFragment(), nodes = [], i; if (jqLiteIsTextNode(html)) { // Convert non-html into a text node nodes.push(context.createTextNode(html)); } else { // Convert html into DOM nodes tmp = tmp || fragment.appendChild(context.createElement("div")); tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase(); wrap = wrapMap[tag] || wrapMap._default; tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1>") + wrap[2]; // Descend through wrappers to the right content i = wrap[0]; while (i--) { tmp = tmp.lastChild; } nodes = concat(nodes, tmp.childNodes); tmp = fragment.firstChild; tmp.textContent = ""; } // Remove wrapper from fragment fragment.textContent = ""; fragment.innerHTML = ""; // Clear inner HTML forEach(nodes, function(node) { fragment.appendChild(node); }); return fragment; } function jqLiteParseHTML(html, context) { context = context || document; var parsed; if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { return [context.createElement(parsed[1])]; } if ((parsed = jqLiteBuildFragment(html, context))) { return parsed.childNodes; } return []; } ///////////////////////////////////////////// function JQLite(element) { if (element instanceof JQLite) { return element; } if (isString(element)) { element = trim(element); } if (!(this instanceof JQLite)) { if (isString(element) && element.charAt(0) != '<') { throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); } return new JQLite(element); } if (isString(element)) { jqLiteAddNodes(this, jqLiteParseHTML(element)); } else { jqLiteAddNodes(this, element); } } function jqLiteClone(element) { return element.cloneNode(true); } function jqLiteDealoc(element){ jqLiteRemoveData(element); for ( var i = 0, children = element.childNodes || []; i < children.length; i++) { jqLiteDealoc(children[i]); } } function jqLiteOff(element, type, fn, unsupported) { if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); var events = jqLiteExpandoStore(element, 'events'), handle = jqLiteExpandoStore(element, 'handle'); if (!handle) return; //no listeners registered if (isUndefined(type)) { forEach(events, function(eventHandler, type) { removeEventListenerFn(element, type, eventHandler); delete events[type]; }); } else { forEach(type.split(' '), function(type) { if (isUndefined(fn)) { removeEventListenerFn(element, type, events[type]); delete events[type]; } else { arrayRemove(events[type] || [], fn); } }); } } function jqLiteRemoveData(element, name) { var expandoId = element[jqName], expandoStore = jqCache[expandoId]; if (expandoStore) { if (name) { delete jqCache[expandoId].data[name]; return; } if (expandoStore.handle) { expandoStore.events.$destroy && expandoStore.handle({}, '$destroy'); jqLiteOff(element); } delete jqCache[expandoId]; element[jqName] = undefined; // ie does not allow deletion of attributes on elements. } } function jqLiteExpandoStore(element, key, value) { var expandoId = element[jqName], expandoStore = jqCache[expandoId || -1]; if (isDefined(value)) { if (!expandoStore) { element[jqName] = expandoId = jqNextId(); expandoStore = jqCache[expandoId] = {}; } expandoStore[key] = value; } else { return expandoStore && expandoStore[key]; } } function jqLiteData(element, key, value) { var data = jqLiteExpandoStore(element, 'data'), isSetter = isDefined(value), keyDefined = !isSetter && isDefined(key), isSimpleGetter = keyDefined && !isObject(key); if (!data && !isSimpleGetter) { jqLiteExpandoStore(element, 'data', data = {}); } if (isSetter) { data[key] = value; } else { if (keyDefined) { if (isSimpleGetter) { // don't create data in this case. return data && data[key]; } else { extend(data, key); } } else { return data; } } } function jqLiteHasClass(element, selector) { if (!element.getAttribute) return false; return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). indexOf( " " + selector + " " ) > -1); } function jqLiteRemoveClass(element, cssClasses) { if (cssClasses && element.setAttribute) { forEach(cssClasses.split(' '), function(cssClass) { element.setAttribute('class', trim( (" " + (element.getAttribute('class') || '') + " ") .replace(/[\n\t]/g, " ") .replace(" " + trim(cssClass) + " ", " ")) ); }); } } function jqLiteAddClass(element, cssClasses) { if (cssClasses && element.setAttribute) { var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') .replace(/[\n\t]/g, " "); forEach(cssClasses.split(' '), function(cssClass) { cssClass = trim(cssClass); if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { existingClasses += cssClass + ' '; } }); element.setAttribute('class', trim(existingClasses)); } } function jqLiteAddNodes(root, elements) { if (elements) { elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements)) ? elements : [ elements ]; for(var i=0; i < elements.length; i++) { root.push(elements[i]); } } } function jqLiteController(element, name) { return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller'); } function jqLiteInheritedData(element, name, value) { element = jqLite(element); // if element is the document object work with the html element instead // this makes $(document).scope() possible if(element[0].nodeType == 9) { element = element.find('html'); } var names = isArray(name) ? name : [name]; while (element.length) { var node = element[0]; for (var i = 0, ii = names.length; i < ii; i++) { if ((value = element.data(names[i])) !== undefined) return value; } // If dealing with a document fragment node with a host element, and no parent, use the host // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM // to lookup parent controllers. element = jqLite(node.parentNode || (node.nodeType === 11 && node.host)); } } function jqLiteEmpty(element) { for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { jqLiteDealoc(childNodes[i]); } while (element.firstChild) { element.removeChild(element.firstChild); } } ////////////////////////////////////////// // Functions which are declared directly. ////////////////////////////////////////// var JQLitePrototype = JQLite.prototype = { ready: function(fn) { var fired = false; function trigger() { if (fired) return; fired = true; fn(); } // check if document already is loaded if (document.readyState === 'complete'){ setTimeout(trigger); } else { this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 // we can not use jqLite since we are not done loading and jQuery could be loaded later. // jshint -W064 JQLite(window).on('load', trigger); // fallback to window.onload for others // jshint +W064 } }, toString: function() { var value = []; forEach(this, function(e){ value.push('' + e);}); return '[' + value.join(', ') + ']'; }, eq: function(index) { return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); }, length: 0, push: push, sort: [].sort, splice: [].splice }; ////////////////////////////////////////// // Functions iterating getter/setters. // these functions return self on setter and // value on get. ////////////////////////////////////////// var BOOLEAN_ATTR = {}; forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { BOOLEAN_ATTR[lowercase(value)] = value; }); var BOOLEAN_ELEMENTS = {}; forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { BOOLEAN_ELEMENTS[uppercase(value)] = true; }); function getBooleanAttrName(element, name) { // check dom last since we will most likely fail on name var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; // booleanAttr is here twice to minimize DOM access return booleanAttr && BOOLEAN_ELEMENTS[element.nodeName] && booleanAttr; } forEach({ data: jqLiteData, inheritedData: jqLiteInheritedData, scope: function(element) { // Can't use jqLiteData here directly so we stay compatible with jQuery! return jqLite(element).data('$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); }, isolateScope: function(element) { // Can't use jqLiteData here directly so we stay compatible with jQuery! return jqLite(element).data('$isolateScope') || jqLite(element).data('$isolateScopeNoTemplate'); }, controller: jqLiteController, injector: function(element) { return jqLiteInheritedData(element, '$injector'); }, removeAttr: function(element,name) { element.removeAttribute(name); }, hasClass: jqLiteHasClass, css: function(element, name, value) { name = camelCase(name); if (isDefined(value)) { element.style[name] = value; } else { var val; if (msie <= 8) { // this is some IE specific weirdness that jQuery 1.6.4 does not sure why val = element.currentStyle && element.currentStyle[name]; if (val === '') val = 'auto'; } val = val || element.style[name]; if (msie <= 8) { // jquery weirdness :-/ val = (val === '') ? undefined : val; } return val; } }, attr: function(element, name, value){ var lowercasedName = lowercase(name); if (BOOLEAN_ATTR[lowercasedName]) { if (isDefined(value)) { if (!!value) { element[name] = true; element.setAttribute(name, lowercasedName); } else { element[name] = false; element.removeAttribute(lowercasedName); } } else { return (element[name] || (element.attributes.getNamedItem(name)|| noop).specified) ? lowercasedName : undefined; } } else if (isDefined(value)) { element.setAttribute(name, value); } else if (element.getAttribute) { // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code // some elements (e.g. Document) don't have get attribute, so return undefined var ret = element.getAttribute(name, 2); // normalize non-existing attributes to undefined (as jQuery) return ret === null ? undefined : ret; } }, prop: function(element, name, value) { if (isDefined(value)) { element[name] = value; } else { return element[name]; } }, text: (function() { var NODE_TYPE_TEXT_PROPERTY = []; if (msie < 9) { NODE_TYPE_TEXT_PROPERTY[1] = 'innerText'; /** Element **/ NODE_TYPE_TEXT_PROPERTY[3] = 'nodeValue'; /** Text **/ } else { NODE_TYPE_TEXT_PROPERTY[1] = /** Element **/ NODE_TYPE_TEXT_PROPERTY[3] = 'textContent'; /** Text **/ } getText.$dv = ''; return getText; function getText(element, value) { var textProp = NODE_TYPE_TEXT_PROPERTY[element.nodeType]; if (isUndefined(value)) { return textProp ? element[textProp] : ''; } element[textProp] = value; } })(), val: function(element, value) { if (isUndefined(value)) { if (nodeName_(element) === 'SELECT' && element.multiple) { var result = []; forEach(element.options, function (option) { if (option.selected) { result.push(option.value || option.text); } }); return result.length === 0 ? null : result; } return element.value; } element.value = value; }, html: function(element, value) { if (isUndefined(value)) { return element.innerHTML; } for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { jqLiteDealoc(childNodes[i]); } element.innerHTML = value; }, empty: jqLiteEmpty }, function(fn, name){ /** * Properties: writes return selection, reads return first value */ JQLite.prototype[name] = function(arg1, arg2) { var i, key; // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it // in a way that survives minification. // jqLiteEmpty takes no arguments but is a setter. if (fn !== jqLiteEmpty && (((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined)) { if (isObject(arg1)) { // we are a write, but the object properties are the key/values for (i = 0; i < this.length; i++) { if (fn === jqLiteData) { // data() takes the whole object in jQuery fn(this[i], arg1); } else { for (key in arg1) { fn(this[i], key, arg1[key]); } } } // return self for chaining return this; } else { // we are a read, so read the first child. var value = fn.$dv; // Only if we have $dv do we iterate over all, otherwise it is just the first element. var jj = (value === undefined) ? Math.min(this.length, 1) : this.length; for (var j = 0; j < jj; j++) { var nodeValue = fn(this[j], arg1, arg2); value = value ? value + nodeValue : nodeValue; } return value; } } else { // we are a write, so apply to all children for (i = 0; i < this.length; i++) { fn(this[i], arg1, arg2); } // return self for chaining return this; } }; }); function createEventHandler(element, events) { var eventHandler = function (event, type) { if (!event.preventDefault) { event.preventDefault = function() { event.returnValue = false; //ie }; } if (!event.stopPropagation) { event.stopPropagation = function() { event.cancelBubble = true; //ie }; } if (!event.target) { event.target = event.srcElement || document; } if (isUndefined(event.defaultPrevented)) { var prevent = event.preventDefault; event.preventDefault = function() { event.defaultPrevented = true; prevent.call(event); }; event.defaultPrevented = false; } event.isDefaultPrevented = function() { return event.defaultPrevented || event.returnValue === false; }; // Copy event handlers in case event handlers array is modified during execution. var eventHandlersCopy = shallowCopy(events[type || event.type] || []); forEach(eventHandlersCopy, function(fn) { fn.call(element, event); }); // Remove monkey-patched methods (IE), // as they would cause memory leaks in IE8. if (msie <= 8) { // IE7/8 does not allow to delete property on native object event.preventDefault = null; event.stopPropagation = null; event.isDefaultPrevented = null; } else { // It shouldn't affect normal browsers (native methods are defined on prototype). delete event.preventDefault; delete event.stopPropagation; delete event.isDefaultPrevented; } }; eventHandler.elem = element; return eventHandler; } ////////////////////////////////////////// // Functions iterating traversal. // These functions chain results into a single // selector. ////////////////////////////////////////// forEach({ removeData: jqLiteRemoveData, dealoc: jqLiteDealoc, on: function onFn(element, type, fn, unsupported){ if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); var events = jqLiteExpandoStore(element, 'events'), handle = jqLiteExpandoStore(element, 'handle'); if (!events) jqLiteExpandoStore(element, 'events', events = {}); if (!handle) jqLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events)); forEach(type.split(' '), function(type){ var eventFns = events[type]; if (!eventFns) { if (type == 'mouseenter' || type == 'mouseleave') { var contains = document.body.contains || document.body.compareDocumentPosition ? function( a, b ) { // jshint bitwise: false var adown = a.nodeType === 9 ? a.documentElement : a, bup = b && b.parentNode; return a === bup || !!( bup && bup.nodeType === 1 && ( adown.contains ? adown.contains( bup ) : a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 )); } : function( a, b ) { if ( b ) { while ( (b = b.parentNode) ) { if ( b === a ) { return true; } } } return false; }; events[type] = []; // Refer to jQuery's implementation of mouseenter & mouseleave // Read about mouseenter and mouseleave: // http://www.quirksmode.org/js/events_mouse.html#link8 var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"}; onFn(element, eventmap[type], function(event) { var target = this, related = event.relatedTarget; // For mousenter/leave call the handler if related is outside the target. // NB: No relatedTarget if the mouse left/entered the browser window if ( !related || (related !== target && !contains(target, related)) ){ handle(event, type); } }); } else { addEventListenerFn(element, type, handle); events[type] = []; } eventFns = events[type]; } eventFns.push(fn); }); }, off: jqLiteOff, one: function(element, type, fn) { element = jqLite(element); //add the listener twice so that when it is called //you can remove the original function and still be //able to call element.off(ev, fn) normally element.on(type, function onFn() { element.off(type, fn); element.off(type, onFn); }); element.on(type, fn); }, replaceWith: function(element, replaceNode) { var index, parent = element.parentNode; jqLiteDealoc(element); forEach(new JQLite(replaceNode), function(node){ if (index) { parent.insertBefore(node, index.nextSibling); } else { parent.replaceChild(node, element); } index = node; }); }, children: function(element) { var children = []; forEach(element.childNodes, function(element){ if (element.nodeType === 1) children.push(element); }); return children; }, contents: function(element) { return element.contentDocument || element.childNodes || []; }, append: function(element, node) { forEach(new JQLite(node), function(child){ if (element.nodeType === 1 || element.nodeType === 11) { element.appendChild(child); } }); }, prepend: function(element, node) { if (element.nodeType === 1) { var index = element.firstChild; forEach(new JQLite(node), function(child){ element.insertBefore(child, index); }); } }, wrap: function(element, wrapNode) { wrapNode = jqLite(wrapNode)[0]; var parent = element.parentNode; if (parent) { parent.replaceChild(wrapNode, element); } wrapNode.appendChild(element); }, remove: function(element) { jqLiteDealoc(element); var parent = element.parentNode; if (parent) parent.removeChild(element); }, after: function(element, newElement) { var index = element, parent = element.parentNode; forEach(new JQLite(newElement), function(node){ parent.insertBefore(node, index.nextSibling); index = node; }); }, addClass: jqLiteAddClass, removeClass: jqLiteRemoveClass, toggleClass: function(element, selector, condition) { if (selector) { forEach(selector.split(' '), function(className){ var classCondition = condition; if (isUndefined(classCondition)) { classCondition = !jqLiteHasClass(element, className); } (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); }); } }, parent: function(element) { var parent = element.parentNode; return parent && parent.nodeType !== 11 ? parent : null; }, next: function(element) { if (element.nextElementSibling) { return element.nextElementSibling; } // IE8 doesn't have nextElementSibling var elm = element.nextSibling; while (elm != null && elm.nodeType !== 1) { elm = elm.nextSibling; } return elm; }, find: function(element, selector) { if (element.getElementsByTagName) { return element.getElementsByTagName(selector); } else { return []; } }, clone: jqLiteClone, triggerHandler: function(element, eventName, eventData) { var eventFns = (jqLiteExpandoStore(element, 'events') || {})[eventName]; eventData = eventData || []; var event = [{ preventDefault: noop, stopPropagation: noop }]; forEach(eventFns, function(fn) { fn.apply(element, event.concat(eventData)); }); } }, function(fn, name){ /** * chaining functions */ JQLite.prototype[name] = function(arg1, arg2, arg3) { var value; for(var i=0; i < this.length; i++) { if (isUndefined(value)) { value = fn(this[i], arg1, arg2, arg3); if (isDefined(value)) { // any function which returns a value needs to be wrapped value = jqLite(value); } } else { jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); } } return isDefined(value) ? value : this; }; // bind legacy bind/unbind to on/off JQLite.prototype.bind = JQLite.prototype.on; JQLite.prototype.unbind = JQLite.prototype.off; }); /** * Computes a hash of an 'obj'. * Hash of a: * string is string * number is number as string * object is either result of calling $$hashKey function on the object or uniquely generated id, * that is also assigned to the $$hashKey property of the object. * * @param obj * @returns {string} hash string such that the same input will have the same hash string. * The resulting string key is in 'type:hashKey' format. */ function hashKey(obj) { var objType = typeof obj, key; if (objType == 'object' && obj !== null) { if (typeof (key = obj.$$hashKey) == 'function') { // must invoke on object to keep the right this key = obj.$$hashKey(); } else if (key === undefined) { key = obj.$$hashKey = nextUid(); } } else { key = obj; } return objType + ':' + key; } /** * HashMap which can use objects as keys */ function HashMap(array){ forEach(array, this.put, this); } HashMap.prototype = { /** * Store key value pair * @param key key to store can be any type * @param value value to store can be any type */ put: function(key, value) { this[hashKey(key)] = value; }, /** * @param key * @returns {Object} the value for the key */ get: function(key) { return this[hashKey(key)]; }, /** * Remove the key/value pair * @param key */ remove: function(key) { var value = this[key = hashKey(key)]; delete this[key]; return value; } }; /** * @ngdoc function * @module ng * @name angular.injector * @function * * @description * Creates an injector function that can be used for retrieving services as well as for * dependency injection (see {@link guide/di dependency injection}). * * @param {Array.} modules A list of module functions or their aliases. See * {@link angular.module}. The `ng` module must be explicitly added. * @returns {function()} Injector function. See {@link auto.$injector $injector}. * * @example * Typical usage * ```js * // create an injector * var $injector = angular.injector(['ng']); * * // use the injector to kick off your application * // use the type inference to auto inject arguments, or use implicit injection * $injector.invoke(function($rootScope, $compile, $document){ * $compile($document)($rootScope); * $rootScope.$digest(); * }); * ``` * * Sometimes you want to get access to the injector of a currently running Angular app * from outside Angular. Perhaps, you want to inject and compile some markup after the * application has been bootstrapped. You can do this using the extra `injector()` added * to JQuery/jqLite elements. See {@link angular.element}. * * *This is fairly rare but could be the case if a third party library is injecting the * markup.* * * In the following example a new block of HTML containing a `ng-controller` * directive is added to the end of the document body by JQuery. We then compile and link * it into the current AngularJS scope. * * ```js * var $div = $('
      {{content.label}}
      '); * $(document.body).append($div); * * angular.element(document).injector().invoke(function($compile) { * var scope = angular.element($div).scope(); * $compile($div)(scope); * }); * ``` */ /** * @ngdoc module * @name auto * @description * * Implicit module which gets automatically added to each {@link auto.$injector $injector}. */ var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; var FN_ARG_SPLIT = /,/; var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; var $injectorMinErr = minErr('$injector'); function anonFn(fn) { // For anonymous functions, showing at the very least the function signature can help in // debugging. var fnText = fn.toString().replace(STRIP_COMMENTS, ''), args = fnText.match(FN_ARGS); if (args) { return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')'; } return 'fn'; } function annotate(fn, strictDi, name) { var $inject, fnText, argDecl, last; if (typeof fn == 'function') { if (!($inject = fn.$inject)) { $inject = []; if (fn.length) { if (strictDi) { if (!isString(name) || !name) { name = fn.name || anonFn(fn); } throw $injectorMinErr('strictdi', '{0} is not using explicit annotation and cannot be invoked in strict mode', name); } fnText = fn.toString().replace(STRIP_COMMENTS, ''); argDecl = fnText.match(FN_ARGS); forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ arg.replace(FN_ARG, function(all, underscore, name){ $inject.push(name); }); }); } fn.$inject = $inject; } } else if (isArray(fn)) { last = fn.length - 1; assertArgFn(fn[last], 'fn'); $inject = fn.slice(0, last); } else { assertArgFn(fn, 'fn', true); } return $inject; } /////////////////////////////////////// /** * @ngdoc service * @name $injector * @function * * @description * * `$injector` is used to retrieve object instances as defined by * {@link auto.$provide provider}, instantiate types, invoke methods, * and load modules. * * The following always holds true: * * ```js * var $injector = angular.injector(); * expect($injector.get('$injector')).toBe($injector); * expect($injector.invoke(function($injector){ * return $injector; * }).toBe($injector); * ``` * * # Injection Function Annotation * * JavaScript does not have annotations, and annotations are needed for dependency injection. The * following are all valid ways of annotating function with injection arguments and are equivalent. * * ```js * // inferred (only works if code not minified/obfuscated) * $injector.invoke(function(serviceA){}); * * // annotated * function explicit(serviceA) {}; * explicit.$inject = ['serviceA']; * $injector.invoke(explicit); * * // inline * $injector.invoke(['serviceA', function(serviceA){}]); * ``` * * ## Inference * * In JavaScript calling `toString()` on a function returns the function definition. The definition * can then be parsed and the function arguments can be extracted. *NOTE:* This does not work with * minification, and obfuscation tools since these tools change the argument names. * * ## `$inject` Annotation * By adding an `$inject` property onto a function the injection parameters can be specified. * * ## Inline * As an array of injection names, where the last item in the array is the function to call. */ /** * @ngdoc method * @name $injector#get * * @description * Return an instance of the service. * * @param {string} name The name of the instance to retrieve. * @return {*} The instance. */ /** * @ngdoc method * @name $injector#invoke * * @description * Invoke the method and supply the method arguments from the `$injector`. * * @param {!Function} fn The function to invoke. Function parameters are injected according to the * {@link guide/di $inject Annotation} rules. * @param {Object=} self The `this` for the invoked method. * @param {Object=} locals Optional object. If preset then any argument names are read from this * object first, before the `$injector` is consulted. * @returns {*} the value returned by the invoked `fn` function. */ /** * @ngdoc method * @name $injector#has * * @description * Allows the user to query if the particular service exists. * * @param {string} Name of the service to query. * @returns {boolean} returns true if injector has given service. */ /** * @ngdoc method * @name $injector#instantiate * @description * Create a new instance of JS type. The method takes a constructor function, invokes the new * operator, and supplies all of the arguments to the constructor function as specified by the * constructor annotation. * * @param {Function} Type Annotated constructor function. * @param {Object=} locals Optional object. If preset then any argument names are read from this * object first, before the `$injector` is consulted. * @returns {Object} new instance of `Type`. */ /** * @ngdoc method * @name $injector#annotate * * @description * Returns an array of service names which the function is requesting for injection. This API is * used by the injector to determine which services need to be injected into the function when the * function is invoked. There are three ways in which the function can be annotated with the needed * dependencies. * * # Argument names * * The simplest form is to extract the dependencies from the arguments of the function. This is done * by converting the function into a string using `toString()` method and extracting the argument * names. * ```js * // Given * function MyController($scope, $route) { * // ... * } * * // Then * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); * ``` * * This method does not work with code minification / obfuscation. For this reason the following * annotation strategies are supported. * * # The `$inject` property * * If a function has an `$inject` property and its value is an array of strings, then the strings * represent names of services to be injected into the function. * ```js * // Given * var MyController = function(obfuscatedScope, obfuscatedRoute) { * // ... * } * // Define function dependencies * MyController['$inject'] = ['$scope', '$route']; * * // Then * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); * ``` * * # The array notation * * It is often desirable to inline Injected functions and that's when setting the `$inject` property * is very inconvenient. In these situations using the array notation to specify the dependencies in * a way that survives minification is a better choice: * * ```js * // We wish to write this (not minification / obfuscation safe) * injector.invoke(function($compile, $rootScope) { * // ... * }); * * // We are forced to write break inlining * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { * // ... * }; * tmpFn.$inject = ['$compile', '$rootScope']; * injector.invoke(tmpFn); * * // To better support inline function the inline annotation is supported * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { * // ... * }]); * * // Therefore * expect(injector.annotate( * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) * ).toEqual(['$compile', '$rootScope']); * ``` * * @param {Function|Array.} fn Function for which dependent service names need to * be retrieved as described above. * * @returns {Array.} The names of the services which the function requires. */ /** * @ngdoc object * @name $provide * * @description * * The {@link auto.$provide $provide} service has a number of methods for registering components * with the {@link auto.$injector $injector}. Many of these functions are also exposed on * {@link angular.Module}. * * An Angular **service** is a singleton object created by a **service factory**. These **service * factories** are functions which, in turn, are created by a **service provider**. * The **service providers** are constructor functions. When instantiated they must contain a * property called `$get`, which holds the **service factory** function. * * When you request a service, the {@link auto.$injector $injector} is responsible for finding the * correct **service provider**, instantiating it and then calling its `$get` **service factory** * function to get the instance of the **service**. * * Often services have no configuration options and there is no need to add methods to the service * provider. The provider will be no more than a constructor function with a `$get` property. For * these cases the {@link auto.$provide $provide} service has additional helper methods to register * services without specifying a provider. * * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the * {@link auto.$injector $injector} * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by * providers and services. * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by * services, not providers. * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`, * that will be wrapped in a **service provider** object, whose `$get` property will contain the * given factory function. * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class` * that will be wrapped in a **service provider** object, whose `$get` property will instantiate * a new object using the given constructor function. * * See the individual methods for more information and examples. */ /** * @ngdoc method * @name $provide#provider * @description * * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions * are constructor functions, whose instances are responsible for "providing" a factory for a * service. * * Service provider names start with the name of the service they provide followed by `Provider`. * For example, the {@link ng.$log $log} service has a provider called * {@link ng.$logProvider $logProvider}. * * Service provider objects can have additional methods which allow configuration of the provider * and its service. Importantly, you can configure what kind of service is created by the `$get` * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a * method {@link ng.$logProvider#debugEnabled debugEnabled} * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the * console or not. * * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 'Provider'` key. * @param {(Object|function())} provider If the provider is: * * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created. * - `Constructor`: a new instance of the provider will be created using * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`. * * @returns {Object} registered provider instance * @example * * The following example shows how to create a simple event tracking service and register it using * {@link auto.$provide#provider $provide.provider()}. * * ```js * // Define the eventTracker provider * function EventTrackerProvider() { * var trackingUrl = '/track'; * * // A provider method for configuring where the tracked events should been saved * this.setTrackingUrl = function(url) { * trackingUrl = url; * }; * * // The service factory function * this.$get = ['$http', function($http) { * var trackedEvents = {}; * return { * // Call this to track an event * event: function(event) { * var count = trackedEvents[event] || 0; * count += 1; * trackedEvents[event] = count; * return count; * }, * // Call this to save the tracked events to the trackingUrl * save: function() { * $http.post(trackingUrl, trackedEvents); * } * }; * }]; * } * * describe('eventTracker', function() { * var postSpy; * * beforeEach(module(function($provide) { * // Register the eventTracker provider * $provide.provider('eventTracker', EventTrackerProvider); * })); * * beforeEach(module(function(eventTrackerProvider) { * // Configure eventTracker provider * eventTrackerProvider.setTrackingUrl('/custom-track'); * })); * * it('tracks events', inject(function(eventTracker) { * expect(eventTracker.event('login')).toEqual(1); * expect(eventTracker.event('login')).toEqual(2); * })); * * it('saves to the tracking url', inject(function(eventTracker, $http) { * postSpy = spyOn($http, 'post'); * eventTracker.event('login'); * eventTracker.save(); * expect(postSpy).toHaveBeenCalled(); * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); * })); * }); * ``` */ /** * @ngdoc method * @name $provide#factory * @description * * Register a **service factory**, which will be called to return the service instance. * This is short for registering a service where its provider consists of only a `$get` property, * which is the given service factory function. * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to * configure your service in a provider. * * @param {string} name The name of the instance. * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand * for `$provide.provider(name, {$get: $getFn})`. * @returns {Object} registered provider instance * * @example * Here is an example of registering a service * ```js * $provide.factory('ping', ['$http', function($http) { * return function ping() { * return $http.send('/ping'); * }; * }]); * ``` * You would then inject and use this service like this: * ```js * someModule.controller('Ctrl', ['ping', function(ping) { * ping(); * }]); * ``` */ /** * @ngdoc method * @name $provide#service * @description * * Register a **service constructor**, which will be invoked with `new` to create the service * instance. * This is short for registering a service where its provider's `$get` property is the service * constructor function that will be used to instantiate the service instance. * * You should use {@link auto.$provide#service $provide.service(class)} if you define your service * as a type/class. * * @param {string} name The name of the instance. * @param {Function} constructor A class (constructor function) that will be instantiated. * @returns {Object} registered provider instance * * @example * Here is an example of registering a service using * {@link auto.$provide#service $provide.service(class)}. * ```js * var Ping = function($http) { * this.$http = $http; * }; * * Ping.$inject = ['$http']; * * Ping.prototype.send = function() { * return this.$http.get('/ping'); * }; * $provide.service('ping', Ping); * ``` * You would then inject and use this service like this: * ```js * someModule.controller('Ctrl', ['ping', function(ping) { * ping.send(); * }]); * ``` */ /** * @ngdoc method * @name $provide#value * @description * * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a * number, an array, an object or a function. This is short for registering a service where its * provider's `$get` property is a factory function that takes no arguments and returns the **value * service**. * * Value services are similar to constant services, except that they cannot be injected into a * module configuration function (see {@link angular.Module#config}) but they can be overridden by * an Angular * {@link auto.$provide#decorator decorator}. * * @param {string} name The name of the instance. * @param {*} value The value. * @returns {Object} registered provider instance * * @example * Here are some examples of creating value services. * ```js * $provide.value('ADMIN_USER', 'admin'); * * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); * * $provide.value('halfOf', function(value) { * return value / 2; * }); * ``` */ /** * @ngdoc method * @name $provide#constant * @description * * Register a **constant service**, such as a string, a number, an array, an object or a function, * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be * injected into a module configuration function (see {@link angular.Module#config}) and it cannot * be overridden by an Angular {@link auto.$provide#decorator decorator}. * * @param {string} name The name of the constant. * @param {*} value The constant value. * @returns {Object} registered instance * * @example * Here a some examples of creating constants: * ```js * $provide.constant('SHARD_HEIGHT', 306); * * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); * * $provide.constant('double', function(value) { * return value * 2; * }); * ``` */ /** * @ngdoc method * @name $provide#decorator * @description * * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator * intercepts the creation of a service, allowing it to override or modify the behaviour of the * service. The object returned by the decorator may be the original service, or a new service * object which replaces or wraps and delegates to the original service. * * @param {string} name The name of the service to decorate. * @param {function()} decorator This function will be invoked when the service needs to be * instantiated and should return the decorated service instance. The function is called using * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. * Local injection arguments: * * * `$delegate` - The original service instance, which can be monkey patched, configured, * decorated or delegated to. * * @example * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting * calls to {@link ng.$log#error $log.warn()}. * ```js * $provide.decorator('$log', ['$delegate', function($delegate) { * $delegate.warn = $delegate.error; * return $delegate; * }]); * ``` */ function createInjector(modulesToLoad, strictDi) { strictDi = (strictDi === true); var INSTANTIATING = {}, providerSuffix = 'Provider', path = [], loadedModules = new HashMap(), providerCache = { $provide: { provider: supportObject(provider), factory: supportObject(factory), service: supportObject(service), value: supportObject(value), constant: supportObject(constant), decorator: decorator } }, providerInjector = (providerCache.$injector = createInternalInjector(providerCache, function() { throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); }, strictDi)), instanceCache = {}, instanceInjector = (instanceCache.$injector = createInternalInjector(instanceCache, function(servicename) { var provider = providerInjector.get(servicename + providerSuffix); return instanceInjector.invoke(provider.$get, provider, undefined, servicename); }, strictDi)); forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); }); return instanceInjector; //////////////////////////////////// // $provider //////////////////////////////////// function supportObject(delegate) { return function(key, value) { if (isObject(key)) { forEach(key, reverseParams(delegate)); } else { return delegate(key, value); } }; } function provider(name, provider_) { assertNotHasOwnProperty(name, 'service'); if (isFunction(provider_) || isArray(provider_)) { provider_ = providerInjector.instantiate(provider_); } if (!provider_.$get) { throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); } return providerCache[name + providerSuffix] = provider_; } function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); } function service(name, constructor) { return factory(name, ['$injector', function($injector) { return $injector.instantiate(constructor); }]); } function value(name, val) { return factory(name, valueFn(val)); } function constant(name, value) { assertNotHasOwnProperty(name, 'constant'); providerCache[name] = value; instanceCache[name] = value; } function decorator(serviceName, decorFn) { var origProvider = providerInjector.get(serviceName + providerSuffix), orig$get = origProvider.$get; origProvider.$get = function() { var origInstance = instanceInjector.invoke(orig$get, origProvider); return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); }; } //////////////////////////////////// // Module Loading //////////////////////////////////// function loadModules(modulesToLoad){ var runBlocks = [], moduleFn, invokeQueue; forEach(modulesToLoad, function(module) { if (loadedModules.get(module)) return; loadedModules.put(module, true); function runInvokeQueue(queue) { var i, ii; for(i = 0, ii = queue.length; i < ii; i++) { var invokeArgs = queue[i], provider = providerInjector.get(invokeArgs[0]); provider[invokeArgs[1]].apply(provider, invokeArgs[2]); } } try { if (isString(module)) { moduleFn = angularModule(module); runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); runInvokeQueue(moduleFn._invokeQueue); runInvokeQueue(moduleFn._configBlocks); } else if (isFunction(module)) { runBlocks.push(providerInjector.invoke(module)); } else if (isArray(module)) { runBlocks.push(providerInjector.invoke(module)); } else { assertArgFn(module, 'module'); } } catch (e) { if (isArray(module)) { module = module[module.length - 1]; } if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { // Safari & FF's stack traces don't contain error.message content // unlike those of Chrome and IE // So if stack doesn't contain message, we create a new string that contains both. // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. /* jshint -W022 */ e = e.message + '\n' + e.stack; } throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", module, e.stack || e.message || e); } }); return runBlocks; } //////////////////////////////////// // internal Injector //////////////////////////////////// function createInternalInjector(cache, factory) { function getService(serviceName) { if (cache.hasOwnProperty(serviceName)) { if (cache[serviceName] === INSTANTIATING) { throw $injectorMinErr('cdep', 'Circular dependency found: {0}', path.join(' <- ')); } return cache[serviceName]; } else { try { path.unshift(serviceName); cache[serviceName] = INSTANTIATING; return cache[serviceName] = factory(serviceName); } catch (err) { if (cache[serviceName] === INSTANTIATING) { delete cache[serviceName]; } throw err; } finally { path.shift(); } } } function invoke(fn, self, locals, serviceName){ if (typeof locals === 'string') { serviceName = locals; locals = null; } var args = [], $inject = annotate(fn, strictDi, serviceName), length, i, key; for(i = 0, length = $inject.length; i < length; i++) { key = $inject[i]; if (typeof key !== 'string') { throw $injectorMinErr('itkn', 'Incorrect injection token! Expected service name as string, got {0}', key); } args.push( locals && locals.hasOwnProperty(key) ? locals[key] : getService(key) ); } if (!fn.$inject) { // this means that we must be an array. fn = fn[length]; } // http://jsperf.com/angularjs-invoke-apply-vs-switch // #5388 return fn.apply(self, args); } function instantiate(Type, locals, serviceName) { var Constructor = function() {}, instance, returnedValue; // Check if Type is annotated and use just the given function at n-1 as parameter // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype; instance = new Constructor(); returnedValue = invoke(Type, instance, locals, serviceName); return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; } return { invoke: invoke, instantiate: instantiate, get: getService, annotate: annotate, has: function(name) { return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); } }; } } createInjector.$$annotate = annotate; /** * @ngdoc service * @name $anchorScroll * @kind function * @requires $window * @requires $location * @requires $rootScope * * @description * When called, it checks current value of `$location.hash()` and scrolls to the related element, * according to rules specified in * [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document). * * It also watches the `$location.hash()` and scrolls whenever it changes to match any anchor. * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`. * * @example
      Go to bottom You're at the bottom!
      function ScrollCtrl($scope, $location, $anchorScroll) { $scope.gotoBottom = function (){ // set the location.hash to the id of // the element you wish to scroll to. $location.hash('bottom'); // call $anchorScroll() $anchorScroll(); }; } #scrollArea { height: 350px; overflow: auto; } #bottom { display: block; margin-top: 2000px; }
      */ function $AnchorScrollProvider() { var autoScrollingEnabled = true; this.disableAutoScrolling = function() { autoScrollingEnabled = false; }; this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { var document = $window.document; // helper function to get first anchor from a NodeList // can't use filter.filter, as it accepts only instances of Array // and IE can't convert NodeList to an array using [].slice // TODO(vojta): use filter if we change it to accept lists as well function getFirstAnchor(list) { var result = null; forEach(list, function(element) { if (!result && lowercase(element.nodeName) === 'a') result = element; }); return result; } function scroll() { var hash = $location.hash(), elm; // empty hash, scroll to the top of the page if (!hash) $window.scrollTo(0, 0); // element with given id else if ((elm = document.getElementById(hash))) elm.scrollIntoView(); // first anchor with given name :-D else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView(); // no element and hash == 'top', scroll to the top of the page else if (hash === 'top') $window.scrollTo(0, 0); } // does not scroll when user clicks on anchor link that is currently on // (no url change, no $location.hash() change), browser native does scroll if (autoScrollingEnabled) { $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, function autoScrollWatchAction() { $rootScope.$evalAsync(scroll); }); } return scroll; }]; } var $animateMinErr = minErr('$animate'); /** * @ngdoc provider * @name $animateProvider * * @description * Default implementation of $animate that doesn't perform any animations, instead just * synchronously performs DOM * updates and calls done() callbacks. * * In order to enable animations the ngAnimate module has to be loaded. * * To see the functional implementation check out src/ngAnimate/animate.js */ var $AnimateProvider = ['$provide', function($provide) { this.$$selectors = {}; /** * @ngdoc method * @name $animateProvider#register * * @description * Registers a new injectable animation factory function. The factory function produces the * animation object which contains callback functions for each event that is expected to be * animated. * * * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction` * must be called once the element animation is complete. If a function is returned then the * animation service will use this function to cancel the animation whenever a cancel event is * triggered. * * * ```js * return { * eventFn : function(element, done) { * //code to run the animation * //once complete, then run done() * return function cancellationFunction() { * //code to cancel the animation * } * } * } * ``` * * @param {string} name The name of the animation. * @param {Function} factory The factory function that will be executed to return the animation * object. */ this.register = function(name, factory) { var key = name + '-animation'; if (name && name.charAt(0) != '.') throw $animateMinErr('notcsel', "Expecting class selector starting with '.' got '{0}'.", name); this.$$selectors[name.substr(1)] = key; $provide.factory(key, factory); }; /** * @ngdoc method * @name $animateProvider#classNameFilter * * @description * Sets and/or returns the CSS class regular expression that is checked when performing * an animation. Upon bootstrap the classNameFilter value is not set at all and will * therefore enable $animate to attempt to perform an animation on any element. * When setting the classNameFilter value, animations will only be performed on elements * that successfully match the filter expression. This in turn can boost performance * for low-powered devices as well as applications containing a lot of structural operations. * @param {RegExp=} expression The className expression which will be checked against all animations * @return {RegExp} The current CSS className expression value. If null then there is no expression value */ this.classNameFilter = function(expression) { if(arguments.length === 1) { this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; } return this.$$classNameFilter; }; this.$get = ['$timeout', '$$asyncCallback', function($timeout, $$asyncCallback) { function async(fn) { fn && $$asyncCallback(fn); } /** * * @ngdoc service * @name $animate * @description The $animate service provides rudimentary DOM manipulation functions to * insert, remove and move elements within the DOM, as well as adding and removing classes. * This service is the core service used by the ngAnimate $animator service which provides * high-level animation hooks for CSS and JavaScript. * * $animate is available in the AngularJS core, however, the ngAnimate module must be included * to enable full out animation support. Otherwise, $animate will only perform simple DOM * manipulation operations. * * To learn more about enabling animation support, click here to visit the {@link ngAnimate * ngAnimate module page} as well as the {@link ngAnimate.$animate ngAnimate $animate service * page}. */ return { /** * * @ngdoc method * @name $animate#enter * @function * @description Inserts the element into the DOM either after the `after` element or * as the first child within the `parent` element. Once complete, the done() callback * will be fired (if provided). * @param {DOMElement} element the element which will be inserted into the DOM * @param {DOMElement} parent the parent element which will append the element as * a child (if the after element is not present) * @param {DOMElement} after the sibling element which will append the element * after itself * @param {Function=} done callback function that will be called after the element has been * inserted into the DOM */ enter : function(element, parent, after, done) { after ? after.after(element) : parent.prepend(element); async(done); }, /** * * @ngdoc method * @name $animate#leave * @function * @description Removes the element from the DOM. Once complete, the done() callback will be * fired (if provided). * @param {DOMElement} element the element which will be removed from the DOM * @param {Function=} done callback function that will be called after the element has been * removed from the DOM */ leave : function(element, done) { element.remove(); async(done); }, /** * * @ngdoc method * @name $animate#move * @function * @description Moves the position of the provided element within the DOM to be placed * either after the `after` element or inside of the `parent` element. Once complete, the * done() callback will be fired (if provided). * * @param {DOMElement} element the element which will be moved around within the * DOM * @param {DOMElement} parent the parent element where the element will be * inserted into (if the after element is not present) * @param {DOMElement} after the sibling element where the element will be * positioned next to * @param {Function=} done the callback function (if provided) that will be fired after the * element has been moved to its new position */ move : function(element, parent, after, done) { // Do not remove element before insert. Removing will cause data associated with the // element to be dropped. Insert will implicitly do the remove. this.enter(element, parent, after, done); }, /** * * @ngdoc method * @name $animate#addClass * @function * @description Adds the provided className CSS class value to the provided element. Once * complete, the done() callback will be fired (if provided). * @param {DOMElement} element the element which will have the className value * added to it * @param {string} className the CSS class which will be added to the element * @param {Function=} done the callback function (if provided) that will be fired after the * className value has been added to the element */ addClass : function(element, className, done) { className = isString(className) ? className : isArray(className) ? className.join(' ') : ''; forEach(element, function (element) { jqLiteAddClass(element, className); }); async(done); }, /** * * @ngdoc method * @name $animate#removeClass * @function * @description Removes the provided className CSS class value from the provided element. * Once complete, the done() callback will be fired (if provided). * @param {DOMElement} element the element which will have the className value * removed from it * @param {string} className the CSS class which will be removed from the element * @param {Function=} done the callback function (if provided) that will be fired after the * className value has been removed from the element */ removeClass : function(element, className, done) { className = isString(className) ? className : isArray(className) ? className.join(' ') : ''; forEach(element, function (element) { jqLiteRemoveClass(element, className); }); async(done); }, /** * * @ngdoc method * @name $animate#setClass * @function * @description Adds and/or removes the given CSS classes to and from the element. * Once complete, the done() callback will be fired (if provided). * @param {DOMElement} element the element which will it's CSS classes changed * removed from it * @param {string} add the CSS classes which will be added to the element * @param {string} remove the CSS class which will be removed from the element * @param {Function=} done the callback function (if provided) that will be fired after the * CSS classes have been set on the element */ setClass : function(element, add, remove, done) { forEach(element, function (element) { jqLiteAddClass(element, add); jqLiteRemoveClass(element, remove); }); async(done); }, enabled : noop }; }]; }]; function $$AsyncCallbackProvider(){ this.$get = ['$$rAF', '$timeout', function($$rAF, $timeout) { return $$rAF.supported ? function(fn) { return $$rAF(fn); } : function(fn) { return $timeout(fn, 0, false); }; }]; } /** * ! This is a private undocumented service ! * * @name $browser * @requires $log * @description * This object has two goals: * * - hide all the global state in the browser caused by the window object * - abstract away all the browser specific features and inconsistencies * * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` * service, which can be used for convenient testing of the application without the interaction with * the real browser apis. */ /** * @param {object} window The global window object. * @param {object} document jQuery wrapped document. * @param {function()} XHR XMLHttpRequest constructor. * @param {object} $log console.log or an object with the same interface. * @param {object} $sniffer $sniffer service */ function Browser(window, document, $log, $sniffer) { var self = this, rawDocument = document[0], location = window.location, history = window.history, setTimeout = window.setTimeout, clearTimeout = window.clearTimeout, pendingDeferIds = {}; self.isMock = false; var outstandingRequestCount = 0; var outstandingRequestCallbacks = []; // TODO(vojta): remove this temporary api self.$$completeOutstandingRequest = completeOutstandingRequest; self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; /** * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. */ function completeOutstandingRequest(fn) { try { fn.apply(null, sliceArgs(arguments, 1)); } finally { outstandingRequestCount--; if (outstandingRequestCount === 0) { while(outstandingRequestCallbacks.length) { try { outstandingRequestCallbacks.pop()(); } catch (e) { $log.error(e); } } } } } /** * @private * Note: this method is used only by scenario runner * TODO(vojta): prefix this method with $$ ? * @param {function()} callback Function that will be called when no outstanding request */ self.notifyWhenNoOutstandingRequests = function(callback) { // force browser to execute all pollFns - this is needed so that cookies and other pollers fire // at some deterministic time in respect to the test runner's actions. Leaving things up to the // regular poller would result in flaky tests. forEach(pollFns, function(pollFn){ pollFn(); }); if (outstandingRequestCount === 0) { callback(); } else { outstandingRequestCallbacks.push(callback); } }; ////////////////////////////////////////////////////////////// // Poll Watcher API ////////////////////////////////////////////////////////////// var pollFns = [], pollTimeout; /** * @name $browser#addPollFn * * @param {function()} fn Poll function to add * * @description * Adds a function to the list of functions that poller periodically executes, * and starts polling if not started yet. * * @returns {function()} the added function */ self.addPollFn = function(fn) { if (isUndefined(pollTimeout)) startPoller(100, setTimeout); pollFns.push(fn); return fn; }; /** * @param {number} interval How often should browser call poll functions (ms) * @param {function()} setTimeout Reference to a real or fake `setTimeout` function. * * @description * Configures the poller to run in the specified intervals, using the specified * setTimeout fn and kicks it off. */ function startPoller(interval, setTimeout) { (function check() { forEach(pollFns, function(pollFn){ pollFn(); }); pollTimeout = setTimeout(check, interval); })(); } ////////////////////////////////////////////////////////////// // URL API ////////////////////////////////////////////////////////////// var lastBrowserUrl = location.href, baseElement = document.find('base'), newLocation = null; /** * @name $browser#url * * @description * GETTER: * Without any argument, this method just returns current value of location.href. * * SETTER: * With at least one argument, this method sets url to new value. * If html5 history api supported, pushState/replaceState is used, otherwise * location.href/location.replace is used. * Returns its own instance to allow chaining * * NOTE: this api is intended for use only by the $location service. Please use the * {@link ng.$location $location service} to change url. * * @param {string} url New url (when used as setter) * @param {boolean=} replace Should new url replace current history record ? */ self.url = function(url, replace) { // Android Browser BFCache causes location, history reference to become stale. if (location !== window.location) location = window.location; if (history !== window.history) history = window.history; // setter if (url) { if (lastBrowserUrl == url) return; lastBrowserUrl = url; if ($sniffer.history) { if (replace) history.replaceState(null, '', url); else { history.pushState(null, '', url); // Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462 baseElement.attr('href', baseElement.attr('href')); } } else { newLocation = url; if (replace) { location.replace(url); } else { location.href = url; } } return self; // getter } else { // - newLocation is a workaround for an IE7-9 issue with location.replace and location.href // methods not updating location.href synchronously. // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 return newLocation || location.href.replace(/%27/g,"'"); } }; var urlChangeListeners = [], urlChangeInit = false; function fireUrlChange() { newLocation = null; if (lastBrowserUrl == self.url()) return; lastBrowserUrl = self.url(); forEach(urlChangeListeners, function(listener) { listener(self.url()); }); } /** * @name $browser#onUrlChange * * @description * Register callback function that will be called, when url changes. * * It's only called when the url is changed from outside of angular: * - user types different url into address bar * - user clicks on history (forward/back) button * - user clicks on a link * * It's not called when url is changed by $browser.url() method * * The listener gets called with new url as parameter. * * NOTE: this api is intended for use only by the $location service. Please use the * {@link ng.$location $location service} to monitor url changes in angular apps. * * @param {function(string)} listener Listener function to be called when url changes. * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. */ self.onUrlChange = function(callback) { // TODO(vojta): refactor to use node's syntax for events if (!urlChangeInit) { // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) // don't fire popstate when user change the address bar and don't fire hashchange when url // changed by push/replaceState // html5 history api - popstate event if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange); // hashchange event if ($sniffer.hashchange) jqLite(window).on('hashchange', fireUrlChange); // polling else self.addPollFn(fireUrlChange); urlChangeInit = true; } urlChangeListeners.push(callback); return callback; }; ////////////////////////////////////////////////////////////// // Misc API ////////////////////////////////////////////////////////////// /** * @name $browser#baseHref * * @description * Returns current * (always relative - without domain) * * @returns {string} The current base href */ self.baseHref = function() { var href = baseElement.attr('href'); return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : ''; }; ////////////////////////////////////////////////////////////// // Cookies API ////////////////////////////////////////////////////////////// var lastCookies = {}; var lastCookieString = ''; var cookiePath = self.baseHref(); /** * @name $browser#cookies * * @param {string=} name Cookie name * @param {string=} value Cookie value * * @description * The cookies method provides a 'private' low level access to browser cookies. * It is not meant to be used directly, use the $cookie service instead. * * The return values vary depending on the arguments that the method was called with as follows: * * - cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify * it * - cookies(name, value) -> set name to value, if value is undefined delete the cookie * - cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that * way) * * @returns {Object} Hash of all cookies (if called without any parameter) */ self.cookies = function(name, value) { /* global escape: false, unescape: false */ var cookieLength, cookieArray, cookie, i, index; if (name) { if (value === undefined) { rawDocument.cookie = escape(name) + "=;path=" + cookiePath + ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; } else { if (isString(value)) { cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + ';path=' + cookiePath).length + 1; // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum: // - 300 cookies // - 20 cookies per unique domain // - 4096 bytes per cookie if (cookieLength > 4096) { $log.warn("Cookie '"+ name + "' possibly not set or overflowed because it was too large ("+ cookieLength + " > 4096 bytes)!"); } } } } else { if (rawDocument.cookie !== lastCookieString) { lastCookieString = rawDocument.cookie; cookieArray = lastCookieString.split("; "); lastCookies = {}; for (i = 0; i < cookieArray.length; i++) { cookie = cookieArray[i]; index = cookie.indexOf('='); if (index > 0) { //ignore nameless cookies name = unescape(cookie.substring(0, index)); // the first value that is seen for a cookie is the most // specific one. values for the same cookie name that // follow are for less specific paths. if (lastCookies[name] === undefined) { lastCookies[name] = unescape(cookie.substring(index + 1)); } } } } return lastCookies; } }; /** * @name $browser#defer * @param {function()} fn A function, who's execution should be deferred. * @param {number=} [delay=0] of milliseconds to defer the function execution. * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. * * @description * Executes a fn asynchronously via `setTimeout(fn, delay)`. * * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed * via `$browser.defer.flush()`. * */ self.defer = function(fn, delay) { var timeoutId; outstandingRequestCount++; timeoutId = setTimeout(function() { delete pendingDeferIds[timeoutId]; completeOutstandingRequest(fn); }, delay || 0); pendingDeferIds[timeoutId] = true; return timeoutId; }; /** * @name $browser#defer.cancel * * @description * Cancels a deferred task identified with `deferId`. * * @param {*} deferId Token returned by the `$browser.defer` function. * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully * canceled. */ self.defer.cancel = function(deferId) { if (pendingDeferIds[deferId]) { delete pendingDeferIds[deferId]; clearTimeout(deferId); completeOutstandingRequest(noop); return true; } return false; }; } function $BrowserProvider(){ this.$get = ['$window', '$log', '$sniffer', '$document', function( $window, $log, $sniffer, $document){ return new Browser($window, $document, $log, $sniffer); }]; } /** * @ngdoc service * @name $cacheFactory * * @description * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to * them. * * ```js * * var cache = $cacheFactory('cacheId'); * expect($cacheFactory.get('cacheId')).toBe(cache); * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); * * cache.put("key", "value"); * cache.put("another key", "another value"); * * // We've specified no options on creation * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); * * ``` * * * @param {string} cacheId Name or id of the newly created cache. * @param {object=} options Options object that specifies the cache behavior. Properties: * * - `{number=}` `capacity` — turns the cache into LRU cache. * * @returns {object} Newly created cache object with the following set of methods: * * - `{object}` `info()` — Returns id, size, and options of cache. * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns * it. * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss. * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache. * - `{void}` `removeAll()` — Removes all cached values. * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory. * * @example

      Cached Values

      :

      Cache Info

      :
      angular.module('cacheExampleApp', []). controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) { $scope.keys = []; $scope.cache = $cacheFactory('cacheId'); $scope.put = function(key, value) { $scope.cache.put(key, value); $scope.keys.push(key); }; }]); p { margin: 10px 0 3px; }
      */ function $CacheFactoryProvider() { this.$get = function() { var caches = {}; function cacheFactory(cacheId, options) { if (cacheId in caches) { throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); } var size = 0, stats = extend({}, options, {id: cacheId}), data = {}, capacity = (options && options.capacity) || Number.MAX_VALUE, lruHash = {}, freshEnd = null, staleEnd = null; /** * @ngdoc type * @name $cacheFactory.Cache * * @description * A cache object used to store and retrieve data, primarily used by * {@link $http $http} and the {@link ng.directive:script script} directive to cache * templates and other data. * * ```js * angular.module('superCache') * .factory('superCache', ['$cacheFactory', function($cacheFactory) { * return $cacheFactory('super-cache'); * }]); * ``` * * Example test: * * ```js * it('should behave like a cache', inject(function(superCache) { * superCache.put('key', 'value'); * superCache.put('another key', 'another value'); * * expect(superCache.info()).toEqual({ * id: 'super-cache', * size: 2 * }); * * superCache.remove('another key'); * expect(superCache.get('another key')).toBeUndefined(); * * superCache.removeAll(); * expect(superCache.info()).toEqual({ * id: 'super-cache', * size: 0 * }); * })); * ``` */ return caches[cacheId] = { /** * @ngdoc method * @name $cacheFactory.Cache#put * @function * * @description * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be * retrieved later, and incrementing the size of the cache if the key was not already * present in the cache. If behaving like an LRU cache, it will also remove stale * entries from the set. * * It will not insert undefined values into the cache. * * @param {string} key the key under which the cached data is stored. * @param {*} value the value to store alongside the key. If it is undefined, the key * will not be stored. * @returns {*} the value stored. */ put: function(key, value) { if (capacity < Number.MAX_VALUE) { var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); refresh(lruEntry); } if (isUndefined(value)) return; if (!(key in data)) size++; data[key] = value; if (size > capacity) { this.remove(staleEnd.key); } return value; }, /** * @ngdoc method * @name $cacheFactory.Cache#get * @function * * @description * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object. * * @param {string} key the key of the data to be retrieved * @returns {*} the value stored. */ get: function(key) { if (capacity < Number.MAX_VALUE) { var lruEntry = lruHash[key]; if (!lruEntry) return; refresh(lruEntry); } return data[key]; }, /** * @ngdoc method * @name $cacheFactory.Cache#remove * @function * * @description * Removes an entry from the {@link $cacheFactory.Cache Cache} object. * * @param {string} key the key of the entry to be removed */ remove: function(key) { if (capacity < Number.MAX_VALUE) { var lruEntry = lruHash[key]; if (!lruEntry) return; if (lruEntry == freshEnd) freshEnd = lruEntry.p; if (lruEntry == staleEnd) staleEnd = lruEntry.n; link(lruEntry.n,lruEntry.p); delete lruHash[key]; } delete data[key]; size--; }, /** * @ngdoc method * @name $cacheFactory.Cache#removeAll * @function * * @description * Clears the cache object of any entries. */ removeAll: function() { data = {}; size = 0; lruHash = {}; freshEnd = staleEnd = null; }, /** * @ngdoc method * @name $cacheFactory.Cache#destroy * @function * * @description * Destroys the {@link $cacheFactory.Cache Cache} object entirely, * removing it from the {@link $cacheFactory $cacheFactory} set. */ destroy: function() { data = null; stats = null; lruHash = null; delete caches[cacheId]; }, /** * @ngdoc method * @name $cacheFactory.Cache#info * @function * * @description * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}. * * @returns {object} an object with the following properties: *
        *
      • **id**: the id of the cache instance
      • *
      • **size**: the number of entries kept in the cache instance
      • *
      • **...**: any additional properties from the options object when creating the * cache.
      • *
      */ info: function() { return extend({}, stats, {size: size}); } }; /** * makes the `entry` the freshEnd of the LRU linked list */ function refresh(entry) { if (entry != freshEnd) { if (!staleEnd) { staleEnd = entry; } else if (staleEnd == entry) { staleEnd = entry.n; } link(entry.n, entry.p); link(entry, freshEnd); freshEnd = entry; freshEnd.n = null; } } /** * bidirectionally links two entries of the LRU linked list */ function link(nextEntry, prevEntry) { if (nextEntry != prevEntry) { if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify } } } /** * @ngdoc method * @name $cacheFactory#info * * @description * Get information about all the caches that have been created * * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` */ cacheFactory.info = function() { var info = {}; forEach(caches, function(cache, cacheId) { info[cacheId] = cache.info(); }); return info; }; /** * @ngdoc method * @name $cacheFactory#get * * @description * Get access to a cache object by the `cacheId` used when it was created. * * @param {string} cacheId Name or id of a cache to access. * @returns {object} Cache object identified by the cacheId or undefined if no such cache. */ cacheFactory.get = function(cacheId) { return caches[cacheId]; }; return cacheFactory; }; } /** * @ngdoc service * @name $templateCache * * @description * The first time a template is used, it is loaded in the template cache for quick retrieval. You * can load templates directly into the cache in a `script` tag, or by consuming the * `$templateCache` service directly. * * Adding via the `script` tag: * * ```html * * ``` * * **Note:** the `script` tag containing the template does not need to be included in the `head` of * the document, but it must be below the `ng-app` definition. * * Adding via the $templateCache service: * * ```js * var myApp = angular.module('myApp', []); * myApp.run(function($templateCache) { * $templateCache.put('templateId.html', 'This is the content of the template'); * }); * ``` * * To retrieve the template later, simply use it in your HTML: * ```html *
      * ``` * * or get it via Javascript: * ```js * $templateCache.get('templateId.html') * ``` * * See {@link ng.$cacheFactory $cacheFactory}. * */ function $TemplateCacheProvider() { this.$get = ['$cacheFactory', function($cacheFactory) { return $cacheFactory('templates'); }]; } /* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! * * DOM-related variables: * * - "node" - DOM Node * - "element" - DOM Element or Node * - "$node" or "$element" - jqLite-wrapped node or element * * * Compiler related stuff: * * - "linkFn" - linking fn of a single directive * - "nodeLinkFn" - function that aggregates all linking fns for a particular node * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) */ /** * @ngdoc service * @name $compile * @function * * @description * Compiles an HTML string or DOM into a template and produces a template function, which * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. * * The compilation is a process of walking the DOM tree and matching DOM elements to * {@link ng.$compileProvider#directive directives}. * *
      * **Note:** This document is an in-depth reference of all directive options. * For a gentle introduction to directives with examples of common use cases, * see the {@link guide/directive directive guide}. *
      * * ## Comprehensive Directive API * * There are many different options for a directive. * * The difference resides in the return value of the factory function. * You can either return a "Directive Definition Object" (see below) that defines the directive properties, * or just the `postLink` function (all other properties will have the default values). * *
      * **Best Practice:** It's recommended to use the "directive definition object" form. *
      * * Here's an example directive declared with a Directive Definition Object: * * ```js * var myModule = angular.module(...); * * myModule.directive('directiveName', function factory(injectables) { * var directiveDefinitionObject = { * priority: 0, * template: '
      ', // or // function(tElement, tAttrs) { ... }, * // or * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, * transclude: false, * restrict: 'A', * scope: false, * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, * controllerAs: 'stringAlias', * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], * compile: function compile(tElement, tAttrs, transclude) { * return { * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, * post: function postLink(scope, iElement, iAttrs, controller) { ... } * } * // or * // return function postLink( ... ) { ... } * }, * // or * // link: { * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, * // post: function postLink(scope, iElement, iAttrs, controller) { ... } * // } * // or * // link: function postLink( ... ) { ... } * }; * return directiveDefinitionObject; * }); * ``` * *
      * **Note:** Any unspecified options will use the default value. You can see the default values below. *
      * * Therefore the above can be simplified as: * * ```js * var myModule = angular.module(...); * * myModule.directive('directiveName', function factory(injectables) { * var directiveDefinitionObject = { * link: function postLink(scope, iElement, iAttrs) { ... } * }; * return directiveDefinitionObject; * // or * // return function postLink(scope, iElement, iAttrs) { ... } * }); * ``` * * * * ### Directive Definition Object * * The directive definition object provides instructions to the {@link ng.$compile * compiler}. The attributes are: * * #### `priority` * When there are multiple directives defined on a single DOM element, sometimes it * is necessary to specify the order in which the directives are applied. The `priority` is used * to sort the directives before their `compile` functions get called. Priority is defined as a * number. Directives with greater numerical `priority` are compiled first. Pre-link functions * are also run in priority order, but post-link functions are run in reverse order. The order * of directives with the same priority is undefined. The default priority is `0`. * * #### `terminal` * If set to true then the current `priority` will be the last set of directives * which will execute (any directives at the current priority will still execute * as the order of execution on same `priority` is undefined). * * #### `scope` * **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the * same element request a new scope, only one new scope is created. The new scope rule does not * apply for the root of the template since the root of the template always gets a new scope. * * **If set to `{}` (object hash),** then a new "isolate" scope is created. The 'isolate' scope differs from * normal scope in that it does not prototypically inherit from the parent scope. This is useful * when creating reusable components, which should not accidentally read or modify data in the * parent scope. * * The 'isolate' scope takes an object hash which defines a set of local scope properties * derived from the parent scope. These local properties are useful for aliasing values for * templates. Locals definition is a hash of local scope property to its source: * * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is * always a string since DOM attributes are strings. If no `attr` name is specified then the * attribute name is assumed to be the same as the local name. * Given `` and widget definition * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the * `localName` property on the widget scope. The `name` is read from the parent scope (not * component scope). * * * `=` or `=attr` - set up bi-directional binding between a local scope property and the * parent scope property of name defined via the value of the `attr` attribute. If no `attr` * name is specified then the attribute name is assumed to be the same as the local name. * Given `` and widget definition of * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. * * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. * If no `attr` name is specified then the attribute name is assumed to be the same as the * local name. Given `` and widget definition of * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to * a function wrapper for the `count = count + value` expression. Often it's desirable to * pass data from the isolated scope via an expression and to the parent scope, this can be * done by passing a map of local variable names and values into the expression wrapper fn. * For example, if the expression is `increment(amount)` then we can specify the amount value * by calling the `localFn` as `localFn({amount: 22})`. * * * * #### `controller` * Controller constructor function. The controller is instantiated before the * pre-linking phase and it is shared with other directives (see * `require` attribute). This allows the directives to communicate with each other and augment * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: * * * `$scope` - Current scope associated with the element * * `$element` - Current element * * `$attrs` - Current attributes object for the element * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope. * The scope can be overridden by an optional first argument. * `function([scope], cloneLinkingFn)`. * * * #### `require` * Require another directive and inject its controller as the fourth argument to the linking function. The * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the * injected argument will be an array in corresponding order. If no such directive can be * found, or if the directive does not have a controller, then an error is raised. The name can be prefixed with: * * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. * * `^` - Locate the required controller by searching the element's parents. Throw an error if not found. * * `?^` - Attempt to locate the required controller by searching the element's parents or pass `null` to the * `link` fn if not found. * * * #### `controllerAs` * Controller alias at the directive scope. An alias for the controller so it * can be referenced at the directive template. The directive needs to define a scope for this * configuration to be used. Useful in the case when directive is used as component. * * * #### `restrict` * String of subset of `EACM` which restricts the directive to a specific directive * declaration style. If omitted, the default (attributes only) is used. * * * `E` - Element name: `` * * `A` - Attribute (default): `
      ` * * `C` - Class: `
      ` * * `M` - Comment: `` * * * #### `type` * String representing the document type used by the markup. This is useful for templates where the root * node is non-HTML content (such as SVG or MathML). The default value is "html". * * * `html` - All root template nodes are HTML, and don't need to be wrapped. Root nodes may also be * top-level elements such as `` or ``. * * `svg` - The template contains only SVG content, and must be wrapped in an `` node prior to * processing. * * `math` - The template contains only MathML content, and must be wrapped in an `` node prior to * processing. * * If no `type` is specified, then the type is considered to be html. * * #### `template` * replace the current element with the contents of the HTML. The replacement process * migrates all of the attributes / classes from the old element to the new one. See the * {@link guide/directive#creating-custom-directives_creating-directives_template-expanding-directive * Directives Guide} for an example. * * You can specify `template` as a string representing the template or as a function which takes * two arguments `tElement` and `tAttrs` (described in the `compile` function api below) and * returns a string value representing the template. * * * #### `templateUrl` * Same as `template` but the template is loaded from the specified URL. Because * the template loading is asynchronous the compilation/linking is suspended until the template * is loaded. * * You can specify `templateUrl` as a string representing the URL or as a function which takes two * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns * a string value representing the url. In either case, the template URL is passed through {@link * api/ng.$sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. * * * #### `replace` ([*DEPRECATED*!], will be removed in next major release) * specify where the template should be inserted. Defaults to `false`. * * * `true` - the template will replace the current element. * * `false` - the template will replace the contents of the current element. * * * #### `transclude` * compile the content of the element and make it available to the directive. * Typically used with {@link ng.directive:ngTransclude * ngTransclude}. The advantage of transclusion is that the linking function receives a * transclusion function which is pre-bound to the correct scope. In a typical setup the widget * creates an `isolate` scope, but the transclusion is not a child, but a sibling of the `isolate` * scope. This makes it possible for the widget to have private state, and the transclusion to * be bound to the parent (pre-`isolate`) scope. * * * `true` - transclude the content of the directive. * * `'element'` - transclude the whole element including any directives defined at lower priority. * * * #### `compile` * * ```js * function compile(tElement, tAttrs, transclude) { ... } * ``` * * The compile function deals with transforming the template DOM. Since most directives do not do * template transformation, it is not used often. The compile function takes the following arguments: * * * `tElement` - template element - The element where the directive has been declared. It is * safe to do template transformation on the element and child elements only. * * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared * between all directive compile functions. * * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` * *
      * **Note:** The template instance and the link instance may be different objects if the template has * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration * should be done in a linking function rather than in a compile function. *
      *
      * **Note:** The compile function cannot handle directives that recursively use themselves in their * own templates or compile functions. Compiling these directives results in an infinite loop and a * stack overflow errors. * * This can be avoided by manually using $compile in the postLink function to imperatively compile * a directive's template instead of relying on automatic template compilation via `template` or * `templateUrl` declaration or manual compilation inside the compile function. *
      * *
      * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it * e.g. does not know about the right outer scope. Please use the transclude function that is passed * to the link function instead. *
      * A compile function can have a return value which can be either a function or an object. * * * returning a (post-link) function - is equivalent to registering the linking function via the * `link` property of the config object when the compile function is empty. * * * returning an object with function(s) registered via `pre` and `post` properties - allows you to * control when a linking function should be called during the linking phase. See info about * pre-linking and post-linking functions below. * * * #### `link` * This property is used only if the `compile` property is not defined. * * ```js * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } * ``` * * The link function is responsible for registering DOM listeners as well as updating the DOM. It is * executed after the template has been cloned. This is where most of the directive logic will be * put. * * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the * directive for registering {@link ng.$rootScope.Scope#$watch watches}. * * * `iElement` - instance element - The element where the directive is to be used. It is safe to * manipulate the children of the element only in `postLink` function since the children have * already been linked. * * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared * between all directive linking functions. * * * `controller` - a controller instance - A controller instance if at least one directive on the * element defines a controller. The controller is shared among all the directives, which allows * the directives to use the controllers as a communication channel. * * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. * The scope can be overridden by an optional first argument. This is the same as the `$transclude` * parameter of directive controllers. * `function([scope], cloneLinkingFn)`. * * * #### Pre-linking function * * Executed before the child elements are linked. Not safe to do DOM transformation since the * compiler linking function will fail to locate the correct elements for linking. * * #### Post-linking function * * Executed after the child elements are linked. It is safe to do DOM transformation in the post-linking function. * * * ### Attributes * * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the * `link()` or `compile()` functions. It has a variety of uses. * * accessing *Normalized attribute names:* * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. * the attributes object allows for normalized access to * the attributes. * * * *Directive inter-communication:* All directives share the same instance of the attributes * object which allows the directives to use the attributes object as inter directive * communication. * * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object * allowing other directives to read the interpolated value. * * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also * the only way to easily get the actual value because during the linking phase the interpolation * hasn't been evaluated yet and so the value is at this time set to `undefined`. * * ```js * function linkingFn(scope, elm, attrs, ctrl) { * // get the attribute value * console.log(attrs.ngModel); * * // change the attribute * attrs.$set('ngModel', 'new value'); * * // observe changes to interpolated attribute * attrs.$observe('ngModel', function(value) { * console.log('ngModel has changed value to ' + value); * }); * } * ``` * * Below is an example using `$compileProvider`. * *
      * **Note**: Typically directives are registered with `module.directive`. The example below is * to illustrate how `$compile` works. *
      *


      it('should auto compile', function() { var textarea = $('textarea'); var output = $('div[compile]'); // The initial state reads 'Hello Angular'. expect(output.getText()).toBe('Hello Angular'); textarea.clear(); textarea.sendKeys('{{name}}!'); expect(output.getText()).toBe('Angular!'); });
      * * * @param {string|DOMElement} element Element or HTML string to compile into a template function. * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives. * @param {number} maxPriority only apply directives lower than given priority (Only effects the * root element(s), not their children) * @returns {function(scope, cloneAttachFn=)} a link function which is used to bind template * (a DOM element/tree) to a scope. Where: * * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the * `template` and call the `cloneAttachFn` function allowing the caller to attach the * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is * called as:
      `cloneAttachFn(clonedElement, scope)` where: * * * `clonedElement` - is a clone of the original `element` passed into the compiler. * * `scope` - is the current scope with which the linking function is working with. * * Calling the linking function returns the element of the template. It is either the original * element passed in, or the clone of the element if the `cloneAttachFn` is provided. * * After linking the view is not updated until after a call to $digest which typically is done by * Angular automatically. * * If you need access to the bound view, there are two ways to do it: * * - If you are not asking the linking function to clone the template, create the DOM element(s) * before you send them to the compiler and keep this reference around. * ```js * var element = $compile('

      {{total}}

      ')(scope); * ``` * * - if on the other hand, you need the element to be cloned, the view reference from the original * example would not point to the clone, but rather to the original template that was cloned. In * this case, you can access the clone via the cloneAttachFn: * ```js * var templateElement = angular.element('

      {{total}}

      '), * scope = ....; * * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { * //attach the clone to DOM document at the right place * }); * * //now we have reference to the cloned DOM via `clonedElement` * ``` * * * For information on how the compiler works, see the * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. */ var $compileMinErr = minErr('$compile'); /** * @ngdoc provider * @name $compileProvider * @function * * @description */ $CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; function $CompileProvider($provide, $$sanitizeUriProvider) { var hasDirectives = {}, Suffix = 'Directive', COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/, CLASS_DIRECTIVE_REGEXP = /(([\d\w_\-]+)(?:\:([^;]+))?;?)/, ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'); // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes // The assumption is that future DOM event attribute names will begin with // 'on' and be composed of only English letters. var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; /** * @ngdoc method * @name $compileProvider#directive * @function * * @description * Register a new directive with the compiler. * * @param {string|Object} name Name of the directive in camel-case (i.e. ngBind which * will match as ng-bind), or an object map of directives where the keys are the * names and the values are the factories. * @param {Function|Array} directiveFactory An injectable directive factory function. See * {@link guide/directive} for more info. * @returns {ng.$compileProvider} Self for chaining. */ this.directive = function registerDirective(name, directiveFactory) { assertNotHasOwnProperty(name, 'directive'); if (isString(name)) { assertArg(directiveFactory, 'directiveFactory'); if (!hasDirectives.hasOwnProperty(name)) { hasDirectives[name] = []; $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', function($injector, $exceptionHandler) { var directives = []; forEach(hasDirectives[name], function(directiveFactory, index) { try { var directive = $injector.invoke(directiveFactory); if (isFunction(directive)) { directive = { compile: valueFn(directive) }; } else if (!directive.compile && directive.link) { directive.compile = valueFn(directive.link); } directive.priority = directive.priority || 0; directive.index = index; directive.name = directive.name || name; directive.require = directive.require || (directive.controller && directive.name); directive.restrict = directive.restrict || 'A'; directives.push(directive); } catch (e) { $exceptionHandler(e); } }); return directives; }]); } hasDirectives[name].push(directiveFactory); } else { forEach(name, reverseParams(registerDirective)); } return this; }; /** * @ngdoc method * @name $compileProvider#aHrefSanitizationWhitelist * @function * * @description * Retrieves or overrides the default regular expression that is used for whitelisting of safe * urls during a[href] sanitization. * * The sanitization is a security measure aimed at prevent XSS attacks via html links. * * Any url about to be assigned to a[href] via data-binding is first normalized and turned into * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` * regular expression. If a match is found, the original url is written into the dom. Otherwise, * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. * * @param {RegExp=} regexp New regexp to whitelist urls with. * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for * chaining otherwise. */ this.aHrefSanitizationWhitelist = function(regexp) { if (isDefined(regexp)) { $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); return this; } else { return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); } }; /** * @ngdoc method * @name $compileProvider#imgSrcSanitizationWhitelist * @function * * @description * Retrieves or overrides the default regular expression that is used for whitelisting of safe * urls during img[src] sanitization. * * The sanitization is a security measure aimed at prevent XSS attacks via html links. * * Any url about to be assigned to img[src] via data-binding is first normalized and turned into * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` * regular expression. If a match is found, the original url is written into the dom. Otherwise, * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. * * @param {RegExp=} regexp New regexp to whitelist urls with. * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for * chaining otherwise. */ this.imgSrcSanitizationWhitelist = function(regexp) { if (isDefined(regexp)) { $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); return this; } else { return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); } }; this.$get = [ '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse', '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri', function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse, $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) { var Attributes = function(element, attr) { this.$$element = element; this.$attr = attr || {}; }; Attributes.prototype = { $normalize: directiveNormalize, /** * @ngdoc method * @name $compile.directive.Attributes#$addClass * @function * * @description * Adds the CSS class value specified by the classVal parameter to the element. If animations * are enabled then an animation will be triggered for the class addition. * * @param {string} classVal The className value that will be added to the element */ $addClass : function(classVal) { if(classVal && classVal.length > 0) { $animate.addClass(this.$$element, classVal); } }, /** * @ngdoc method * @name $compile.directive.Attributes#$removeClass * @function * * @description * Removes the CSS class value specified by the classVal parameter from the element. If * animations are enabled then an animation will be triggered for the class removal. * * @param {string} classVal The className value that will be removed from the element */ $removeClass : function(classVal) { if(classVal && classVal.length > 0) { $animate.removeClass(this.$$element, classVal); } }, /** * @ngdoc method * @name $compile.directive.Attributes#$updateClass * @function * * @description * Adds and removes the appropriate CSS class values to the element based on the difference * between the new and old CSS class values (specified as newClasses and oldClasses). * * @param {string} newClasses The current CSS className value * @param {string} oldClasses The former CSS className value */ $updateClass : function(newClasses, oldClasses) { var toAdd = tokenDifference(newClasses, oldClasses); var toRemove = tokenDifference(oldClasses, newClasses); if(toAdd.length === 0) { $animate.removeClass(this.$$element, toRemove); } else if(toRemove.length === 0) { $animate.addClass(this.$$element, toAdd); } else { $animate.setClass(this.$$element, toAdd, toRemove); } }, /** * Set a normalized attribute on the element in a way such that all directives * can share the attribute. This function properly handles boolean attributes. * @param {string} key Normalized key. (ie ngAttribute) * @param {string|boolean} value The value to set. If `null` attribute will be deleted. * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. * Defaults to true. * @param {string=} attrName Optional none normalized name. Defaults to key. */ $set: function(key, value, writeAttr, attrName) { // TODO: decide whether or not to throw an error if "class" //is set through this function since it may cause $updateClass to //become unstable. var booleanKey = getBooleanAttrName(this.$$element[0], key), normalizedVal, nodeName; if (booleanKey) { this.$$element.prop(key, value); attrName = booleanKey; } this[key] = value; // translate normalized key to actual key if (attrName) { this.$attr[key] = attrName; } else { attrName = this.$attr[key]; if (!attrName) { this.$attr[key] = attrName = snake_case(key, '-'); } } nodeName = nodeName_(this.$$element); // sanitize a[href] and img[src] values if ((nodeName === 'A' && key === 'href') || (nodeName === 'IMG' && key === 'src')) { this[key] = value = $$sanitizeUri(value, key === 'src'); } if (writeAttr !== false) { if (value === null || value === undefined) { this.$$element.removeAttr(attrName); } else { this.$$element.attr(attrName, value); } } // fire observers var $$observers = this.$$observers; $$observers && forEach($$observers[key], function(fn) { try { fn(value); } catch (e) { $exceptionHandler(e); } }); }, /** * @ngdoc method * @name $compile.directive.Attributes#$observe * @function * * @description * Observes an interpolated attribute. * * The observer function will be invoked once during the next `$digest` following * compilation. The observer is then invoked whenever the interpolated value * changes. * * @param {string} key Normalized key. (ie ngAttribute) . * @param {function(interpolatedValue)} fn Function that will be called whenever the interpolated value of the attribute changes. * See the {@link guide/directive#Attributes Directives} guide for more info. * @returns {function()} Returns a deregistration function for this observer. */ $observe: function(key, fn) { var attrs = this, $$observers = (attrs.$$observers || (attrs.$$observers = {})), listeners = ($$observers[key] || ($$observers[key] = [])); listeners.push(fn); $rootScope.$evalAsync(function() { if (!listeners.$$inter) { // no one registered attribute interpolation function, so lets call it manually fn(attrs[key]); } }); return function() { arrayRemove(listeners, fn); }; } }; var startSymbol = $interpolate.startSymbol(), endSymbol = $interpolate.endSymbol(), denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') ? identity : function denormalizeTemplate(template) { return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); }, NG_ATTR_BINDING = /^ngAttr[A-Z]/; return compile; //================================ function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) { if (!($compileNodes instanceof jqLite)) { // jquery always rewraps, whereas we need to preserve the original selector so that we can // modify it. $compileNodes = jqLite($compileNodes); } // We can not compile top level text elements since text nodes can be merged and we will // not be able to attach scope data to them, so we will wrap them in forEach($compileNodes, function(node, index){ if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) { $compileNodes[index] = node = jqLite(node).wrap('').parent()[0]; } }); var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority, ignoreDirective, previousCompileContext); safeAddClass($compileNodes, 'ng-scope'); return function publicLinkFn(scope, cloneConnectFn, transcludeControllers){ assertArg(scope, 'scope'); // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart // and sometimes changes the structure of the DOM. var $linkNode = cloneConnectFn ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!! : $compileNodes; forEach(transcludeControllers, function(instance, name) { $linkNode.data('$' + name + 'Controller', instance); }); // Attach scope only to non-text nodes. for(var i = 0, ii = $linkNode.length; i addDirective(directives, directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective); // iterate over the attributes for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes, j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { var attrStartName = false; var attrEndName = false; attr = nAttrs[j]; if (!msie || msie >= 8 || attr.specified) { name = attr.name; // support ngAttr attribute binding ngAttrName = directiveNormalize(name); if (NG_ATTR_BINDING.test(ngAttrName)) { name = snake_case(ngAttrName.substr(6), '-'); } var directiveNName = ngAttrName.replace(/(Start|End)$/, ''); if (ngAttrName === directiveNName + 'Start') { attrStartName = name; attrEndName = name.substr(0, name.length - 5) + 'end'; name = name.substr(0, name.length - 6); } nName = directiveNormalize(name.toLowerCase()); attrsMap[nName] = name; attrs[nName] = value = trim(attr.value); if (getBooleanAttrName(node, nName)) { attrs[nName] = true; // presence means true } addAttrInterpolateDirective(node, directives, value, nName); addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, attrEndName); } } // use class as directive className = node.className; if (isString(className) && className !== '') { while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { nName = directiveNormalize(match[2]); if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { attrs[nName] = trim(match[3]); } className = className.substr(match.index + match[0].length); } } break; case 3: /* Text Node */ addTextInterpolateDirective(directives, node.nodeValue); break; case 8: /* Comment */ try { match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); if (match) { nName = directiveNormalize(match[1]); if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { attrs[nName] = trim(match[2]); } } } catch (e) { // turns out that under some circumstances IE9 throws errors when one attempts to read // comment's node value. // Just ignore it and continue. (Can't seem to reproduce in test case.) } break; } directives.sort(byPriority); return directives; } /** * Given a node with an directive-start it collects all of the siblings until it finds * directive-end. * @param node * @param attrStart * @param attrEnd * @returns {*} */ function groupScan(node, attrStart, attrEnd) { var nodes = []; var depth = 0; if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { var startNode = node; do { if (!node) { throw $compileMinErr('uterdir', "Unterminated attribute, found '{0}' but no matching '{1}' found.", attrStart, attrEnd); } if (node.nodeType == 1 /** Element **/) { if (node.hasAttribute(attrStart)) depth++; if (node.hasAttribute(attrEnd)) depth--; } nodes.push(node); node = node.nextSibling; } while (depth > 0); } else { nodes.push(node); } return jqLite(nodes); } /** * Wrapper for linking function which converts normal linking function into a grouped * linking function. * @param linkFn * @param attrStart * @param attrEnd * @returns {Function} */ function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { return function(scope, element, attrs, controllers, transcludeFn) { element = groupScan(element[0], attrStart, attrEnd); return linkFn(scope, element, attrs, controllers, transcludeFn); }; } /** * Once the directives have been collected, their compile functions are executed. This method * is responsible for inlining directive templates as well as terminating the application * of the directives if the terminal directive has been reached. * * @param {Array} directives Array of collected directives to execute their compile function. * this needs to be pre-sorted by priority order. * @param {Node} compileNode The raw DOM node to apply the compile functions to * @param {Object} templateAttrs The shared attribute function * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the * scope argument is auto-generated to the new * child of the transcluded parent scope. * @param {JQLite} jqCollection If we are working on the root of the compile tree then this * argument has the root jqLite array so that we can replace nodes * on it. * @param {Object=} originalReplaceDirective An optional directive that will be ignored when * compiling the transclusion. * @param {Array.} preLinkFns * @param {Array.} postLinkFns * @param {Object} previousCompileContext Context used for previous compilation of the current * node * @returns {Function} linkFn */ function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, previousCompileContext) { previousCompileContext = previousCompileContext || {}; var terminalPriority = -Number.MAX_VALUE, newScopeDirective, controllerDirectives = previousCompileContext.controllerDirectives, newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, templateDirective = previousCompileContext.templateDirective, nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, hasTranscludeDirective = false, hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, $compileNode = templateAttrs.$$element = jqLite(compileNode), directive, directiveName, $template, replaceDirective = originalReplaceDirective, childTranscludeFn = transcludeFn, linkFn, directiveValue; // executes all directives on the current element for(var i = 0, ii = directives.length; i < ii; i++) { directive = directives[i]; var attrStart = directive.$$start; var attrEnd = directive.$$end; // collect multiblock sections if (attrStart) { $compileNode = groupScan(compileNode, attrStart, attrEnd); } $template = undefined; if (terminalPriority > directive.priority) { break; // prevent further processing of directives } if (directiveValue = directive.scope) { newScopeDirective = newScopeDirective || directive; // skip the check for directives with async templates, we'll check the derived sync // directive when the template arrives if (!directive.templateUrl) { assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, $compileNode); if (isObject(directiveValue)) { newIsolateScopeDirective = directive; } } } directiveName = directive.name; if (!directive.templateUrl && directive.controller) { directiveValue = directive.controller; controllerDirectives = controllerDirectives || {}; assertNoDuplicate("'" + directiveName + "' controller", controllerDirectives[directiveName], directive, $compileNode); controllerDirectives[directiveName] = directive; } if (directiveValue = directive.transclude) { hasTranscludeDirective = true; // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. // This option should only be used by directives that know how to safely handle element transclusion, // where the transcluded nodes are added or replaced after linking. if (!directive.$$tlb) { assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); nonTlbTranscludeDirective = directive; } if (directiveValue == 'element') { hasElementTranscludeDirective = true; terminalPriority = directive.priority; $template = groupScan(compileNode, attrStart, attrEnd); $compileNode = templateAttrs.$$element = jqLite(document.createComment(' ' + directiveName + ': ' + templateAttrs[directiveName] + ' ')); compileNode = $compileNode[0]; replaceWith(jqCollection, jqLite(sliceArgs($template)), compileNode); childTranscludeFn = compile($template, transcludeFn, terminalPriority, replaceDirective && replaceDirective.name, { // Don't pass in: // - controllerDirectives - otherwise we'll create duplicates controllers // - newIsolateScopeDirective or templateDirective - combining templates with // element transclusion doesn't make sense. // // We need only nonTlbTranscludeDirective so that we prevent putting transclusion // on the same element more than once. nonTlbTranscludeDirective: nonTlbTranscludeDirective }); } else { $template = jqLite(jqLiteClone(compileNode)).contents(); $compileNode.empty(); // clear contents childTranscludeFn = compile($template, transcludeFn); } } if (directive.template) { assertNoDuplicate('template', templateDirective, directive, $compileNode); templateDirective = directive; directiveValue = (isFunction(directive.template)) ? directive.template($compileNode, templateAttrs) : directive.template; directiveValue = denormalizeTemplate(directiveValue); if (directive.replace) { replaceDirective = directive; if (jqLiteIsTextNode(directiveValue)) { $template = []; } else { $template = jqLite(wrapTemplate(directive.type, trim(directiveValue))); } compileNode = $template[0]; if ($template.length != 1 || compileNode.nodeType !== 1) { throw $compileMinErr('tplrt', "Template for directive '{0}' must have exactly one root element. {1}", directiveName, ''); } replaceWith(jqCollection, $compileNode, compileNode); var newTemplateAttrs = {$attr: {}}; // combine directives from the original node and from the template: // - take the array of directives for this element // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) // - collect directives from the template and sort them by priority // - combine directives as: processed + template + unprocessed var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); if (newIsolateScopeDirective) { markDirectivesAsIsolate(templateDirectives); } directives = directives.concat(templateDirectives).concat(unprocessedDirectives); mergeTemplateAttributes(templateAttrs, newTemplateAttrs); ii = directives.length; } else { $compileNode.html(directiveValue); } } if (directive.templateUrl) { assertNoDuplicate('template', templateDirective, directive, $compileNode); templateDirective = directive; if (directive.replace) { replaceDirective = directive; } nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, templateAttrs, jqCollection, childTranscludeFn, preLinkFns, postLinkFns, { controllerDirectives: controllerDirectives, newIsolateScopeDirective: newIsolateScopeDirective, templateDirective: templateDirective, nonTlbTranscludeDirective: nonTlbTranscludeDirective }); ii = directives.length; } else if (directive.compile) { try { linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); if (isFunction(linkFn)) { addLinkFns(null, linkFn, attrStart, attrEnd); } else if (linkFn) { addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); } } catch (e) { $exceptionHandler(e, startingTag($compileNode)); } } if (directive.terminal) { nodeLinkFn.terminal = true; terminalPriority = Math.max(terminalPriority, directive.priority); } } nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; nodeLinkFn.transclude = hasTranscludeDirective && childTranscludeFn; previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; // might be normal or delayed nodeLinkFn depending on if templateUrl is present return nodeLinkFn; //////////////////// function addLinkFns(pre, post, attrStart, attrEnd) { if (pre) { if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); pre.require = directive.require; pre.directiveName = directiveName; if (newIsolateScopeDirective === directive || directive.$$isolateScope) { pre = cloneAndAnnotateFn(pre, {isolateScope: true}); } preLinkFns.push(pre); } if (post) { if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); post.require = directive.require; post.directiveName = directiveName; if (newIsolateScopeDirective === directive || directive.$$isolateScope) { post = cloneAndAnnotateFn(post, {isolateScope: true}); } postLinkFns.push(post); } } function getControllers(directiveName, require, $element, elementControllers) { var value, retrievalMethod = 'data', optional = false; if (isString(require)) { while((value = require.charAt(0)) == '^' || value == '?') { require = require.substr(1); if (value == '^') { retrievalMethod = 'inheritedData'; } optional = optional || value == '?'; } value = null; if (elementControllers && retrievalMethod === 'data') { value = elementControllers[require]; } value = value || $element[retrievalMethod]('$' + require + 'Controller'); if (!value && !optional) { throw $compileMinErr('ctreq', "Controller '{0}', required by directive '{1}', can't be found!", require, directiveName); } return value; } else if (isArray(require)) { value = []; forEach(require, function(require) { value.push(getControllers(directiveName, require, $element, elementControllers)); }); } return value; } function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { var attrs, $element, i, ii, linkFn, controller, isolateScope, elementControllers = {}, transcludeFn; if (compileNode === linkNode) { attrs = templateAttrs; } else { attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr)); } $element = attrs.$$element; if (newIsolateScopeDirective) { var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/; var $linkNode = jqLite(linkNode); isolateScope = scope.$new(true); if (templateDirective && (templateDirective === newIsolateScopeDirective || templateDirective === newIsolateScopeDirective.$$originalDirective)) { $linkNode.data('$isolateScope', isolateScope) ; } else { $linkNode.data('$isolateScopeNoTemplate', isolateScope); } safeAddClass($linkNode, 'ng-isolate-scope'); forEach(newIsolateScopeDirective.scope, function(definition, scopeName) { var match = definition.match(LOCAL_REGEXP) || [], attrName = match[3] || scopeName, optional = (match[2] == '?'), mode = match[1], // @, =, or & lastValue, parentGet, parentSet, compare; isolateScope.$$isolateBindings[scopeName] = mode + attrName; switch (mode) { case '@': attrs.$observe(attrName, function(value) { isolateScope[scopeName] = value; }); attrs.$$observers[attrName].$$scope = scope; if( attrs[attrName] ) { // If the attribute has been provided then we trigger an interpolation to ensure // the value is there for use in the link fn isolateScope[scopeName] = $interpolate(attrs[attrName])(scope); } break; case '=': if (optional && !attrs[attrName]) { return; } parentGet = $parse(attrs[attrName]); if (parentGet.literal) { compare = equals; } else { compare = function(a,b) { return a === b; }; } parentSet = parentGet.assign || function() { // reset the change, or we will throw this exception on every $digest lastValue = isolateScope[scopeName] = parentGet(scope); throw $compileMinErr('nonassign', "Expression '{0}' used with directive '{1}' is non-assignable!", attrs[attrName], newIsolateScopeDirective.name); }; lastValue = isolateScope[scopeName] = parentGet(scope); isolateScope.$watch(function parentValueWatch() { var parentValue = parentGet(scope); if (!compare(parentValue, isolateScope[scopeName])) { // we are out of sync and need to copy if (!compare(parentValue, lastValue)) { // parent changed and it has precedence isolateScope[scopeName] = parentValue; } else { // if the parent can be assigned then do so parentSet(scope, parentValue = isolateScope[scopeName]); } } parentValueWatch.$$unwatch = parentGet.$$unwatch; return lastValue = parentValue; }, null, parentGet.literal); break; case '&': parentGet = $parse(attrs[attrName]); isolateScope[scopeName] = function(locals) { return parentGet(scope, locals); }; break; default: throw $compileMinErr('iscp', "Invalid isolate scope definition for directive '{0}'." + " Definition: {... {1}: '{2}' ...}", newIsolateScopeDirective.name, scopeName, definition); } }); } transcludeFn = boundTranscludeFn && controllersBoundTransclude; if (controllerDirectives) { forEach(controllerDirectives, function(directive) { var locals = { $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, $element: $element, $attrs: attrs, $transclude: transcludeFn }, controllerInstance; controller = directive.controller; if (controller == '@') { controller = attrs[directive.name]; } controllerInstance = $controller(controller, locals); // For directives with element transclusion the element is a comment, // but jQuery .data doesn't support attaching data to comment nodes as it's hard to // clean up (http://bugs.jquery.com/ticket/8335). // Instead, we save the controllers for the element in a local hash and attach to .data // later, once we have the actual element. elementControllers[directive.name] = controllerInstance; if (!hasElementTranscludeDirective) { $element.data('$' + directive.name + 'Controller', controllerInstance); } if (directive.controllerAs) { locals.$scope[directive.controllerAs] = controllerInstance; } }); } // PRELINKING for(i = 0, ii = preLinkFns.length; i < ii; i++) { try { linkFn = preLinkFns[i]; linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn); } catch (e) { $exceptionHandler(e, startingTag($element)); } } // RECURSION // We only pass the isolate scope, if the isolate directive has a template, // otherwise the child elements do not belong to the isolate directive. var scopeToChild = scope; if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { scopeToChild = isolateScope; } childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); // POSTLINKING for(i = postLinkFns.length - 1; i >= 0; i--) { try { linkFn = postLinkFns[i]; linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn); } catch (e) { $exceptionHandler(e, startingTag($element)); } } // This is the function that is injected as `$transclude`. function controllersBoundTransclude(scope, cloneAttachFn) { var transcludeControllers; // no scope passed if (arguments.length < 2) { cloneAttachFn = scope; scope = undefined; } if (hasElementTranscludeDirective) { transcludeControllers = elementControllers; } return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers); } } } function markDirectivesAsIsolate(directives) { // mark all directives as needing isolate scope. for (var j = 0, jj = directives.length; j < jj; j++) { directives[j] = inherit(directives[j], {$$isolateScope: true}); } } /** * looks up the directive and decorates it with exception handling and proper parameters. We * call this the boundDirective. * * @param {string} name name of the directive to look up. * @param {string} location The directive must be found in specific format. * String containing any of theses characters: * * * `E`: element name * * `A': attribute * * `C`: class * * `M`: comment * @returns {boolean} true if directive was added. */ function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, endAttrName) { if (name === ignoreDirective) return null; var match = null; if (hasDirectives.hasOwnProperty(name)) { for(var directive, directives = $injector.get(name + Suffix), i = 0, ii = directives.length; i directive.priority) && directive.restrict.indexOf(location) != -1) { if (startAttrName) { directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); } tDirectives.push(directive); match = directive; } } catch(e) { $exceptionHandler(e); } } } return match; } /** * When the element is replaced with HTML template then the new attributes * on the template need to be merged with the existing attributes in the DOM. * The desired effect is to have both of the attributes present. * * @param {object} dst destination attributes (original DOM) * @param {object} src source attributes (from the directive template) */ function mergeTemplateAttributes(dst, src) { var srcAttr = src.$attr, dstAttr = dst.$attr, $element = dst.$$element; // reapply the old attributes to the new element forEach(dst, function(value, key) { if (key.charAt(0) != '$') { if (src[key] && src[key] !== value) { value += (key === 'style' ? ';' : ' ') + src[key]; } dst.$set(key, value, true, srcAttr[key]); } }); // copy the new attributes on the old attrs object forEach(src, function(value, key) { if (key == 'class') { safeAddClass($element, value); dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; } else if (key == 'style') { $element.attr('style', $element.attr('style') + ';' + value); dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value; // `dst` will never contain hasOwnProperty as DOM parser won't let it. // You will get an "InvalidCharacterError: DOM Exception 5" error if you // have an attribute like "has-own-property" or "data-has-own-property", etc. } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { dst[key] = value; dstAttr[key] = srcAttr[key]; } }); } function compileTemplateUrl(directives, $compileNode, tAttrs, $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { var linkQueue = [], afterTemplateNodeLinkFn, afterTemplateChildLinkFn, beforeTemplateCompileNode = $compileNode[0], origAsyncDirective = directives.shift(), // The fact that we have to copy and patch the directive seems wrong! derivedSyncDirective = extend({}, origAsyncDirective, { templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective }), templateUrl = (isFunction(origAsyncDirective.templateUrl)) ? origAsyncDirective.templateUrl($compileNode, tAttrs) : origAsyncDirective.templateUrl, type = origAsyncDirective.type; $compileNode.empty(); $http.get($sce.getTrustedResourceUrl(templateUrl), {cache: $templateCache}). success(function(content) { var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; content = denormalizeTemplate(content); if (origAsyncDirective.replace) { if (jqLiteIsTextNode(content)) { $template = []; } else { $template = jqLite(wrapTemplate(type, trim(content))); } compileNode = $template[0]; if ($template.length != 1 || compileNode.nodeType !== 1) { throw $compileMinErr('tplrt', "Template for directive '{0}' must have exactly one root element. {1}", origAsyncDirective.name, templateUrl); } tempTemplateAttrs = {$attr: {}}; replaceWith($rootElement, $compileNode, compileNode); var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); if (isObject(origAsyncDirective.scope)) { markDirectivesAsIsolate(templateDirectives); } directives = templateDirectives.concat(directives); mergeTemplateAttributes(tAttrs, tempTemplateAttrs); } else { compileNode = beforeTemplateCompileNode; $compileNode.html(content); } directives.unshift(derivedSyncDirective); afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, previousCompileContext); forEach($rootElement, function(node, i) { if (node == compileNode) { $rootElement[i] = $compileNode[0]; } }); afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); while(linkQueue.length) { var scope = linkQueue.shift(), beforeTemplateLinkNode = linkQueue.shift(), linkRootElement = linkQueue.shift(), boundTranscludeFn = linkQueue.shift(), linkNode = $compileNode[0]; if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { var oldClasses = beforeTemplateLinkNode.className; if (!(previousCompileContext.hasElementTranscludeDirective && origAsyncDirective.replace)) { // it was cloned therefore we have to clone as well. linkNode = jqLiteClone(compileNode); } replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); // Copy in CSS classes from original node safeAddClass(jqLite(linkNode), oldClasses); } if (afterTemplateNodeLinkFn.transclude) { childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude); } else { childBoundTranscludeFn = boundTranscludeFn; } afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, childBoundTranscludeFn); } linkQueue = null; }). error(function(response, code, headers, config) { throw $compileMinErr('tpload', 'Failed to load template: {0}', config.url); }); return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { if (linkQueue) { linkQueue.push(scope); linkQueue.push(node); linkQueue.push(rootElement); linkQueue.push(boundTranscludeFn); } else { afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, boundTranscludeFn); } }; } /** * Sorting function for bound directives. */ function byPriority(a, b) { var diff = b.priority - a.priority; if (diff !== 0) return diff; if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; return a.index - b.index; } function assertNoDuplicate(what, previousDirective, directive, element) { if (previousDirective) { throw $compileMinErr('multidir', 'Multiple directives [{0}, {1}] asking for {2} on: {3}', previousDirective.name, directive.name, what, startingTag(element)); } } function addTextInterpolateDirective(directives, text) { var interpolateFn = $interpolate(text, true); if (interpolateFn) { directives.push({ priority: 0, compile: valueFn(function textInterpolateLinkFn(scope, node) { var parent = node.parent(), bindings = parent.data('$binding') || []; // Need to interpolate again in case this is using one-time bindings in multiple clones // of transcluded templates. interpolateFn = $interpolate(text); bindings.push(interpolateFn); safeAddClass(parent.data('$binding', bindings), 'ng-binding'); scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { node[0].nodeValue = value; }); }) }); } } function wrapTemplate(type, template) { type = lowercase(type || 'html'); switch(type) { case 'svg': case 'math': var wrapper = document.createElement('div'); wrapper.innerHTML = '<'+type+'>'+template+''; return wrapper.childNodes[0].childNodes; default: return template; } } function getTrustedContext(node, attrNormalizedName) { if (attrNormalizedName == "srcdoc") { return $sce.HTML; } var tag = nodeName_(node); // maction[xlink:href] can source SVG. It's not limited to . if (attrNormalizedName == "xlinkHref" || (tag == "FORM" && attrNormalizedName == "action") || (tag != "IMG" && (attrNormalizedName == "src" || attrNormalizedName == "ngSrc"))) { return $sce.RESOURCE_URL; } } function addAttrInterpolateDirective(node, directives, value, name) { var interpolateFn = $interpolate(value, true); // no interpolation found -> ignore if (!interpolateFn) return; if (name === "multiple" && nodeName_(node) === "SELECT") { throw $compileMinErr("selmulti", "Binding to the 'multiple' attribute is not supported. Element: {0}", startingTag(node)); } directives.push({ priority: 100, compile: function() { return { pre: function attrInterpolatePreLinkFn(scope, element, attr) { var $$observers = (attr.$$observers || (attr.$$observers = {})); if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { throw $compileMinErr('nodomevents', "Interpolations for HTML DOM event attributes are disallowed. Please use the " + "ng- versions (such as ng-click instead of onclick) instead."); } // we need to interpolate again, in case the attribute value has been updated // (e.g. by another directive's compile function) interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name), ALL_OR_NOTHING_ATTRS[name]); // if attribute was updated so that there is no interpolation going on we don't want to // register any observers if (!interpolateFn) return; // initialize attr object so that it's ready in case we need the value for isolate // scope initialization, otherwise the value would not be available from isolate // directive's linking fn during linking phase attr[name] = interpolateFn(scope); ($$observers[name] || ($$observers[name] = [])).$$inter = true; (attr.$$observers && attr.$$observers[name].$$scope || scope). $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { //special case for class attribute addition + removal //so that class changes can tap into the animation //hooks provided by the $animate service. Be sure to //skip animations when the first digest occurs (when //both the new and the old values are the same) since //the CSS classes are the non-interpolated values if(name === 'class' && newValue != oldValue) { attr.$updateClass(newValue, oldValue); } else { attr.$set(name, newValue); } }); } }; } }); } /** * This is a special jqLite.replaceWith, which can replace items which * have no parents, provided that the containing jqLite collection is provided. * * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes * in the root of the tree. * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep * the shell, but replace its DOM node reference. * @param {Node} newNode The new DOM node. */ function replaceWith($rootElement, elementsToRemove, newNode) { var firstElementToRemove = elementsToRemove[0], removeCount = elementsToRemove.length, parent = firstElementToRemove.parentNode, i, ii; if ($rootElement) { for(i = 0, ii = $rootElement.length; i < ii; i++) { if ($rootElement[i] == firstElementToRemove) { $rootElement[i++] = newNode; for (var j = i, j2 = j + removeCount - 1, jj = $rootElement.length; j < jj; j++, j2++) { if (j2 < jj) { $rootElement[j] = $rootElement[j2]; } else { delete $rootElement[j]; } } $rootElement.length -= removeCount - 1; break; } } } if (parent) { parent.replaceChild(newNode, firstElementToRemove); } var fragment = document.createDocumentFragment(); fragment.appendChild(firstElementToRemove); newNode[jqLite.expando] = firstElementToRemove[jqLite.expando]; for (var k = 1, kk = elementsToRemove.length; k < kk; k++) { var element = elementsToRemove[k]; jqLite(element).remove(); // must do this way to clean up expando fragment.appendChild(element); delete elementsToRemove[k]; } elementsToRemove[0] = newNode; elementsToRemove.length = 1; } function cloneAndAnnotateFn(fn, annotation) { return extend(function() { return fn.apply(null, arguments); }, fn, annotation); } }]; } var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i; /** * Converts all accepted directives format into proper directive name. * All of these will become 'myDirective': * my:Directive * my-directive * x-my-directive * data-my:directive * * Also there is special case for Moz prefix starting with upper case letter. * @param name Name to normalize */ function directiveNormalize(name) { return camelCase(name.replace(PREFIX_REGEXP, '')); } /** * @ngdoc type * @name $compile.directive.Attributes * * @description * A shared object between directive compile / linking functions which contains normalized DOM * element attributes. The values reflect current binding state `{{ }}`. The normalization is * needed since all of these are treated as equivalent in Angular: * * ``` * * ``` */ /** * @ngdoc property * @name $compile.directive.Attributes#$attr * @returns {object} A map of DOM element attribute names to the normalized name. This is * needed to do reverse lookup from normalized name back to actual name. */ /** * @ngdoc method * @name $compile.directive.Attributes#$set * @function * * @description * Set DOM element attribute value. * * * @param {string} name Normalized element attribute name of the property to modify. The name is * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} * property to the original name. * @param {string} value Value to set the attribute to. The value can be an interpolated string. */ /** * Closure compiler type information */ function nodesetLinkingFn( /* angular.Scope */ scope, /* NodeList */ nodeList, /* Element */ rootElement, /* function(Function) */ boundTranscludeFn ){} function directiveLinkingFn( /* nodesetLinkingFn */ nodesetLinkingFn, /* angular.Scope */ scope, /* Node */ node, /* Element */ rootElement, /* function(Function) */ boundTranscludeFn ){} function tokenDifference(str1, str2) { var values = '', tokens1 = str1.split(/\s+/), tokens2 = str2.split(/\s+/); outer: for(var i = 0; i < tokens1.length; i++) { var token = tokens1[i]; for(var j = 0; j < tokens2.length; j++) { if(token == tokens2[j]) continue outer; } values += (values.length > 0 ? ' ' : '') + token; } return values; } /** * @ngdoc provider * @name $controllerProvider * @description * The {@link ng.$controller $controller service} is used by Angular to create new * controllers. * * This provider allows controller registration via the * {@link ng.$controllerProvider#register register} method. */ function $ControllerProvider() { var controllers = {}, CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/; /** * @ngdoc method * @name $controllerProvider#register * @param {string|Object} name Controller name, or an object map of controllers where the keys are * the names and the values are the constructors. * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI * annotations in the array notation). */ this.register = function(name, constructor) { assertNotHasOwnProperty(name, 'controller'); if (isObject(name)) { extend(controllers, name); } else { controllers[name] = constructor; } }; this.$get = ['$injector', '$window', function($injector, $window) { /** * @ngdoc service * @name $controller * @requires $injector * * @param {Function|string} constructor If called with a function then it's considered to be the * controller constructor function. Otherwise it's considered to be a string which is used * to retrieve the controller constructor using the following steps: * * * check if a controller with given name is registered via `$controllerProvider` * * check if evaluating the string on the current scope returns a constructor * * check `window[constructor]` on the global `window` object * * @param {Object} locals Injection locals for Controller. * @return {Object} Instance of given controller. * * @description * `$controller` service is responsible for instantiating controllers. * * It's just a simple call to {@link auto.$injector $injector}, but extracted into * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). */ return function(expression, locals) { var instance, match, constructor, identifier; if(isString(expression)) { match = expression.match(CNTRL_REG), constructor = match[1], identifier = match[3]; expression = controllers.hasOwnProperty(constructor) ? controllers[constructor] : getter(locals.$scope, constructor, true) || getter($window, constructor, true); assertArgFn(expression, constructor, true); } instance = $injector.instantiate(expression, locals, constructor); if (identifier) { if (!(locals && typeof locals.$scope == 'object')) { throw minErr('$controller')('noscp', "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", constructor || expression.name, identifier); } locals.$scope[identifier] = instance; } return instance; }; }]; } /** * @ngdoc service * @name $document * @requires $window * * @description * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. * * @example

      $document title:

      window.document title:

      function MainCtrl($scope, $document) { $scope.title = $document[0].title; $scope.windowTitle = angular.element(window.document)[0].title; }
      */ function $DocumentProvider(){ this.$get = ['$window', function(window){ return jqLite(window.document); }]; } /** * @ngdoc service * @name $exceptionHandler * @requires ng.$log * * @description * Any uncaught exception in angular expressions is delegated to this service. * The default implementation simply delegates to `$log.error` which logs it into * the browser console. * * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. * * ## Example: * * ```js * angular.module('exceptionOverride', []).factory('$exceptionHandler', function () { * return function (exception, cause) { * exception.message += ' (caused by "' + cause + '")'; * throw exception; * }; * }); * ``` * * This example will override the normal action of `$exceptionHandler`, to make angular * exceptions fail hard when they happen, instead of just logging to the console. * * @param {Error} exception Exception associated with the error. * @param {string=} cause optional information about the context in which * the error was thrown. * */ function $ExceptionHandlerProvider() { this.$get = ['$log', function($log) { return function(exception, cause) { $log.error.apply($log, arguments); }; }]; } /** * Parse headers into key value object * * @param {string} headers Raw headers as a string * @returns {Object} Parsed headers as key value object */ function parseHeaders(headers) { var parsed = {}, key, val, i; if (!headers) return parsed; forEach(headers.split('\n'), function(line) { i = line.indexOf(':'); key = lowercase(trim(line.substr(0, i))); val = trim(line.substr(i + 1)); if (key) { if (parsed[key]) { parsed[key] += ', ' + val; } else { parsed[key] = val; } } }); return parsed; } /** * Returns a function that provides access to parsed headers. * * Headers are lazy parsed when first requested. * @see parseHeaders * * @param {(string|Object)} headers Headers to provide access to. * @returns {function(string=)} Returns a getter function which if called with: * * - if called with single an argument returns a single header value or null * - if called with no arguments returns an object containing all headers. */ function headersGetter(headers) { var headersObj = isObject(headers) ? headers : undefined; return function(name) { if (!headersObj) headersObj = parseHeaders(headers); if (name) { return headersObj[lowercase(name)] || null; } return headersObj; }; } /** * Chain all given functions * * This function is used for both request and response transforming * * @param {*} data Data to transform. * @param {function(string=)} headers Http headers getter fn. * @param {(Function|Array.)} fns Function or an array of functions. * @returns {*} Transformed data. */ function transformData(data, headers, fns) { if (isFunction(fns)) return fns(data, headers); forEach(fns, function(fn) { data = fn(data, headers); }); return data; } function isSuccess(status) { return 200 <= status && status < 300; } function $HttpProvider() { var JSON_START = /^\s*(\[|\{[^\{])/, JSON_END = /[\}\]]\s*$/, PROTECTION_PREFIX = /^\)\]\}',?\n/, CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'}; var defaults = this.defaults = { // transform incoming response data transformResponse: [function(data) { if (isString(data)) { // strip json vulnerability protection prefix data = data.replace(PROTECTION_PREFIX, ''); if (JSON_START.test(data) && JSON_END.test(data)) data = fromJson(data); } return data; }], // transform outgoing request data transformRequest: [function(d) { return isObject(d) && !isFile(d) && !isBlob(d) ? toJson(d) : d; }], // default headers headers: { common: { 'Accept': 'application/json, text/plain, */*' }, post: copy(CONTENT_TYPE_APPLICATION_JSON), put: copy(CONTENT_TYPE_APPLICATION_JSON), patch: copy(CONTENT_TYPE_APPLICATION_JSON) }, xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN' }; /** * Are ordered by request, i.e. they are applied in the same order as the * array, on request, but reverse order, on response. */ var interceptorFactories = this.interceptors = []; this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) { var defaultCache = $cacheFactory('$http'); /** * Interceptors stored in reverse order. Inner interceptors before outer interceptors. * The reversal is needed so that we can build up the interception chain around the * server request. */ var reversedInterceptors = []; forEach(interceptorFactories, function(interceptorFactory) { reversedInterceptors.unshift(isString(interceptorFactory) ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); }); /** * @ngdoc service * @kind function * @name $http * @requires ng.$httpBackend * @requires $cacheFactory * @requires $rootScope * @requires $q * @requires $injector * * @description * The `$http` service is a core Angular service that facilitates communication with the remote * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). * * For unit testing applications that use `$http` service, see * {@link ngMock.$httpBackend $httpBackend mock}. * * For a higher level of abstraction, please check out the {@link ngResource.$resource * $resource} service. * * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage * it is important to familiarize yourself with these APIs and the guarantees they provide. * * * # General usage * The `$http` service is a function which takes a single argument — a configuration object — * that is used to generate an HTTP request and returns a {@link ng.$q promise} * with two $http specific methods: `success` and `error`. * * ```js * $http({method: 'GET', url: '/someUrl'}). * success(function(data, status, headers, config) { * // this callback will be called asynchronously * // when the response is available * }). * error(function(data, status, headers, config) { * // called asynchronously if an error occurs * // or server returns response with an error status. * }); * ``` * * Since the returned value of calling the $http function is a `promise`, you can also use * the `then` method to register callbacks, and these callbacks will receive a single argument – * an object representing the response. See the API signature and type info below for more * details. * * A response status code between 200 and 299 is considered a success status and * will result in the success callback being called. Note that if the response is a redirect, * XMLHttpRequest will transparently follow it, meaning that the error callback will not be * called for such responses. * * # Writing Unit Tests that use $http * When unit testing (using {@link ngMock ngMock}), it is necessary to call * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending * request using trained responses. * * ``` * $httpBackend.expectGET(...); * $http.get(...); * $httpBackend.flush(); * ``` * * # Shortcut methods * * Shortcut methods are also available. All shortcut methods require passing in the URL, and * request data must be passed in for POST/PUT requests. * * ```js * $http.get('/someUrl').success(successCallback); * $http.post('/someUrl', data).success(successCallback); * ``` * * Complete list of shortcut methods: * * - {@link ng.$http#get $http.get} * - {@link ng.$http#head $http.head} * - {@link ng.$http#post $http.post} * - {@link ng.$http#put $http.put} * - {@link ng.$http#delete $http.delete} * - {@link ng.$http#jsonp $http.jsonp} * * * # Setting HTTP Headers * * The $http service will automatically add certain HTTP headers to all requests. These defaults * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration * object, which currently contains this default configuration: * * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): * - `Accept: application/json, text/plain, * / *` * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) * - `Content-Type: application/json` * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) * - `Content-Type: application/json` * * To add or overwrite these defaults, simply add or remove a property from these configuration * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object * with the lowercased HTTP method name as the key, e.g. * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }. * * The defaults can also be set at runtime via the `$http.defaults` object in the same * fashion. For example: * * ``` * module.run(function($http) { * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w' * }); * ``` * * In addition, you can supply a `headers` property in the config object passed when * calling `$http(config)`, which overrides the defaults without changing them globally. * * * # Transforming Requests and Responses * * Both requests and responses can be transformed using transform functions. By default, Angular * applies these transformations: * * Request transformations: * * - If the `data` property of the request configuration object contains an object, serialize it * into JSON format. * * Response transformations: * * - If XSRF prefix is detected, strip it (see Security Considerations section below). * - If JSON response is detected, deserialize it using a JSON parser. * * To globally augment or override the default transforms, modify the * `$httpProvider.defaults.transformRequest` and `$httpProvider.defaults.transformResponse` * properties. These properties are by default an array of transform functions, which allows you * to `push` or `unshift` a new transformation function into the transformation chain. You can * also decide to completely override any default transformations by assigning your * transformation functions to these properties directly without the array wrapper. These defaults * are again available on the $http factory at run-time, which may be useful if you have run-time * services you wish to be involved in your transformations. * * Similarly, to locally override the request/response transforms, augment the * `transformRequest` and/or `transformResponse` properties of the configuration object passed * into `$http`. * * * # Caching * * To enable caching, set the request configuration `cache` property to `true` (to use default * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}). * When the cache is enabled, `$http` stores the response from the server in the specified * cache. The next time the same request is made, the response is served from the cache without * sending a request to the server. * * Note that even if the response is served from cache, delivery of the data is asynchronous in * the same way that real requests are. * * If there are multiple GET requests for the same URL that should be cached using the same * cache, but the cache is not populated yet, only one request to the server will be made and * the remaining requests will be fulfilled using the response from the first request. * * You can change the default cache to a new object (built with * {@link ng.$cacheFactory `$cacheFactory`}) by updating the * {@link ng.$http#properties_defaults `$http.defaults.cache`} property. All requests who set * their `cache` property to `true` will now use this cache object. * * If you set the default cache to `false` then only requests that specify their own custom * cache object will be cached. * * # Interceptors * * Before you start creating interceptors, be sure to understand the * {@link ng.$q $q and deferred/promise APIs}. * * For purposes of global error handling, authentication, or any kind of synchronous or * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be * able to intercept requests before they are handed to the server and * responses before they are handed over to the application code that * initiated these requests. The interceptors leverage the {@link ng.$q * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. * * The interceptors are service factories that are registered with the `$httpProvider` by * adding them to the `$httpProvider.interceptors` array. The factory is called and * injected with dependencies (if specified) and returns the interceptor. * * There are two kinds of interceptors (and two kinds of rejection interceptors): * * * `request`: interceptors get called with a http `config` object. The function is free to * modify the `config` object or create a new one. The function needs to return the `config` * object directly, or a promise containing the `config` or a new `config` object. * * `requestError`: interceptor gets called when a previous interceptor threw an error or * resolved with a rejection. * * `response`: interceptors get called with http `response` object. The function is free to * modify the `response` object or create a new one. The function needs to return the `response` * object directly, or as a promise containing the `response` or a new `response` object. * * `responseError`: interceptor gets called when a previous interceptor threw an error or * resolved with a rejection. * * * ```js * // register the interceptor as a service * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { * return { * // optional method * 'request': function(config) { * // do something on success * return config; * }, * * // optional method * 'requestError': function(rejection) { * // do something on error * if (canRecover(rejection)) { * return responseOrNewPromise * } * return $q.reject(rejection); * }, * * * * // optional method * 'response': function(response) { * // do something on success * return response; * }, * * // optional method * 'responseError': function(rejection) { * // do something on error * if (canRecover(rejection)) { * return responseOrNewPromise * } * return $q.reject(rejection); * } * }; * }); * * $httpProvider.interceptors.push('myHttpInterceptor'); * * * // alternatively, register the interceptor via an anonymous factory * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { * return { * 'request': function(config) { * // same as above * }, * * 'response': function(response) { * // same as above * } * }; * }); * ``` * * # Security Considerations * * When designing web applications, consider security threats from: * * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) * * Both server and the client must cooperate in order to eliminate these threats. Angular comes * pre-configured with strategies that address these issues, but for this to work backend server * cooperation is required. * * ## JSON Vulnerability Protection * * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) * allows third party website to turn your JSON resource URL into * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To * counter this your server can prefix all JSON requests with following string `")]}',\n"`. * Angular will automatically strip the prefix before processing it as JSON. * * For example if your server needs to return: * ```js * ['one','two'] * ``` * * which is vulnerable to attack, your server can return: * ```js * )]}', * ['one','two'] * ``` * * Angular will strip the prefix, before processing the JSON. * * * ## Cross Site Request Forgery (XSRF) Protection * * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which * an unauthorized site can gain your user's private data. Angular provides a mechanism * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only * JavaScript that runs on your domain could read the cookie, your server can be assured that * the XHR came from JavaScript running on your domain. The header will not be set for * cross-domain requests. * * To take advantage of this, your server needs to set a token in a JavaScript readable session * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure * that only JavaScript running on your domain could have sent the request. The token must be * unique for each user and must be verifiable by the server (to prevent the JavaScript from * making up its own tokens). We recommend that the token is a digest of your site's * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) * for added security. * * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, * or the per-request config object. * * * @param {object} config Object describing the request to be made and how it should be * processed. The object has following properties: * * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc) * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested. * - **params** – `{Object.}` – Map of strings or objects which will be turned * to `?key1=value1&key2=value2` after the url. If the value is not a string, it will be * JSONified. * - **data** – `{string|Object}` – Data to be sent as the request message data. * - **headers** – `{Object}` – Map of strings or functions which return strings representing * HTTP headers to send to the server. If the return value of a function is null, the * header will not be sent. * - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token. * - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token. * - **transformRequest** – * `{function(data, headersGetter)|Array.}` – * transform function or an array of such functions. The transform function takes the http * request body and headers and returns its transformed (typically serialized) version. * - **transformResponse** – * `{function(data, headersGetter)|Array.}` – * transform function or an array of such functions. The transform function takes the http * response body and headers and returns its transformed (typically deserialized) version. * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the * GET request, otherwise if a cache instance built with * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for * caching. * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} * that should abort the request when resolved. * - **withCredentials** - `{boolean}` - whether to to set the `withCredentials` flag on the * XHR object. See [requests with credentials]https://developer.mozilla.org/en/http_access_control#section_5 * for more information. * - **responseType** - `{string}` - see * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType). * * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the * standard `then` method and two http specific methods: `success` and `error`. The `then` * method takes two arguments a success and an error callback which will be called with a * response object. The `success` and `error` methods take a single argument - a function that * will be called when the request succeeds or fails respectively. The arguments passed into * these functions are destructured representation of the response object passed into the * `then` method. The response object has these properties: * * - **data** – `{string|Object}` – The response body transformed with the transform * functions. * - **status** – `{number}` – HTTP status code of the response. * - **headers** – `{function([headerName])}` – Header getter function. * - **config** – `{Object}` – The configuration object that was used to generate the request. * - **statusText** – `{string}` – HTTP status text of the response. * * @property {Array.} pendingRequests Array of config objects for currently pending * requests. This is primarily meant to be used for debugging purposes. * * * @example

      http status code: {{status}}
      http response data: {{data}}
      function FetchCtrl($scope, $http, $templateCache) { $scope.method = 'GET'; $scope.url = 'http-hello.html'; $scope.fetch = function() { $scope.code = null; $scope.response = null; $http({method: $scope.method, url: $scope.url, cache: $templateCache}). success(function(data, status) { $scope.status = status; $scope.data = data; }). error(function(data, status) { $scope.data = data || "Request failed"; $scope.status = status; }); }; $scope.updateModel = function(method, url) { $scope.method = method; $scope.url = url; }; } Hello, $http! var status = element(by.binding('status')); var data = element(by.binding('data')); var fetchBtn = element(by.id('fetchbtn')); var sampleGetBtn = element(by.id('samplegetbtn')); var sampleJsonpBtn = element(by.id('samplejsonpbtn')); var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); it('should make an xhr GET request', function() { sampleGetBtn.click(); fetchBtn.click(); expect(status.getText()).toMatch('200'); expect(data.getText()).toMatch(/Hello, \$http!/); }); it('should make a JSONP request to angularjs.org', function() { sampleJsonpBtn.click(); fetchBtn.click(); expect(status.getText()).toMatch('200'); expect(data.getText()).toMatch(/Super Hero!/); }); it('should make JSONP request to invalid URL and invoke the error handler', function() { invalidJsonpBtn.click(); fetchBtn.click(); expect(status.getText()).toMatch('0'); expect(data.getText()).toMatch('Request failed'); });
      */ function $http(requestConfig) { var config = { method: 'get', transformRequest: defaults.transformRequest, transformResponse: defaults.transformResponse }; var headers = mergeHeaders(requestConfig); extend(config, requestConfig); config.headers = headers; config.method = uppercase(config.method); var xsrfValue = urlIsSameOrigin(config.url) ? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName] : undefined; if (xsrfValue) { headers[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; } var serverRequest = function(config) { headers = config.headers; var reqData = transformData(config.data, headersGetter(headers), config.transformRequest); // strip content-type if data is undefined if (isUndefined(config.data)) { forEach(headers, function(value, header) { if (lowercase(header) === 'content-type') { delete headers[header]; } }); } if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { config.withCredentials = defaults.withCredentials; } // send request return sendReq(config, reqData, headers).then(transformResponse, transformResponse); }; var chain = [serverRequest, undefined]; var promise = $q.when(config); // apply interceptors forEach(reversedInterceptors, function(interceptor) { if (interceptor.request || interceptor.requestError) { chain.unshift(interceptor.request, interceptor.requestError); } if (interceptor.response || interceptor.responseError) { chain.push(interceptor.response, interceptor.responseError); } }); while(chain.length) { var thenFn = chain.shift(); var rejectFn = chain.shift(); promise = promise.then(thenFn, rejectFn); } promise.success = function(fn) { promise.then(function(response) { fn(response.data, response.status, response.headers, config); }); return promise; }; promise.error = function(fn) { promise.then(null, function(response) { fn(response.data, response.status, response.headers, config); }); return promise; }; return promise; function transformResponse(response) { // make a copy since the response must be cacheable var resp = extend({}, response, { data: transformData(response.data, response.headers, config.transformResponse) }); return (isSuccess(response.status)) ? resp : $q.reject(resp); } function mergeHeaders(config) { var defHeaders = defaults.headers, reqHeaders = extend({}, config.headers), defHeaderName, lowercaseDefHeaderName, reqHeaderName; defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); // execute if header value is function execHeaders(defHeaders); execHeaders(reqHeaders); // using for-in instead of forEach to avoid unecessary iteration after header has been found defaultHeadersIteration: for (defHeaderName in defHeaders) { lowercaseDefHeaderName = lowercase(defHeaderName); for (reqHeaderName in reqHeaders) { if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { continue defaultHeadersIteration; } } reqHeaders[defHeaderName] = defHeaders[defHeaderName]; } return reqHeaders; function execHeaders(headers) { var headerContent; forEach(headers, function(headerFn, header) { if (isFunction(headerFn)) { headerContent = headerFn(); if (headerContent != null) { headers[header] = headerContent; } else { delete headers[header]; } } }); } } } $http.pendingRequests = []; /** * @ngdoc method * @name $http#get * * @description * Shortcut method to perform `GET` request. * * @param {string} url Relative or absolute URL specifying the destination of the request * @param {Object=} config Optional configuration object * @returns {HttpPromise} Future object */ /** * @ngdoc method * @name $http#delete * * @description * Shortcut method to perform `DELETE` request. * * @param {string} url Relative or absolute URL specifying the destination of the request * @param {Object=} config Optional configuration object * @returns {HttpPromise} Future object */ /** * @ngdoc method * @name $http#head * * @description * Shortcut method to perform `HEAD` request. * * @param {string} url Relative or absolute URL specifying the destination of the request * @param {Object=} config Optional configuration object * @returns {HttpPromise} Future object */ /** * @ngdoc method * @name $http#jsonp * * @description * Shortcut method to perform `JSONP` request. * * @param {string} url Relative or absolute URL specifying the destination of the request. * Should contain `JSON_CALLBACK` string. * @param {Object=} config Optional configuration object * @returns {HttpPromise} Future object */ createShortMethods('get', 'delete', 'head', 'jsonp'); /** * @ngdoc method * @name $http#post * * @description * Shortcut method to perform `POST` request. * * @param {string} url Relative or absolute URL specifying the destination of the request * @param {*} data Request content * @param {Object=} config Optional configuration object * @returns {HttpPromise} Future object */ /** * @ngdoc method * @name $http#put * * @description * Shortcut method to perform `PUT` request. * * @param {string} url Relative or absolute URL specifying the destination of the request * @param {*} data Request content * @param {Object=} config Optional configuration object * @returns {HttpPromise} Future object */ createShortMethodsWithData('post', 'put'); /** * @ngdoc property * @name $http#defaults * * @description * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of * default headers, withCredentials as well as request and response transformations. * * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. */ $http.defaults = defaults; return $http; function createShortMethods(names) { forEach(arguments, function(name) { $http[name] = function(url, config) { return $http(extend(config || {}, { method: name, url: url })); }; }); } function createShortMethodsWithData(name) { forEach(arguments, function(name) { $http[name] = function(url, data, config) { return $http(extend(config || {}, { method: name, url: url, data: data })); }; }); } /** * Makes the request. * * !!! ACCESSES CLOSURE VARS: * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests */ function sendReq(config, reqData, reqHeaders) { var deferred = $q.defer(), promise = deferred.promise, cache, cachedResp, url = buildUrl(config.url, config.params); $http.pendingRequests.push(config); promise.then(removePendingReq, removePendingReq); if ((config.cache || defaults.cache) && config.cache !== false && config.method == 'GET') { cache = isObject(config.cache) ? config.cache : isObject(defaults.cache) ? defaults.cache : defaultCache; } if (cache) { cachedResp = cache.get(url); if (isDefined(cachedResp)) { if (cachedResp.then) { // cached request has already been sent, but there is no response yet cachedResp.then(removePendingReq, removePendingReq); return cachedResp; } else { // serving from cache if (isArray(cachedResp)) { resolvePromise(cachedResp[1], cachedResp[0], copy(cachedResp[2]), cachedResp[3]); } else { resolvePromise(cachedResp, 200, {}, 'OK'); } } } else { // put the promise for the non-transformed response into cache as a placeholder cache.put(url, promise); } } // if we won't have the response in cache, send the request to the backend if (isUndefined(cachedResp)) { $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, config.withCredentials, config.responseType); } return promise; /** * Callback registered to $httpBackend(): * - caches the response if desired * - resolves the raw $http promise * - calls $apply */ function done(status, response, headersString, statusText) { if (cache) { if (isSuccess(status)) { cache.put(url, [status, response, parseHeaders(headersString), statusText]); } else { // remove promise from the cache cache.remove(url); } } resolvePromise(response, status, headersString, statusText); if (!$rootScope.$$phase) $rootScope.$apply(); } /** * Resolves the raw $http promise. */ function resolvePromise(response, status, headers, statusText) { // normalize internal statuses to 0 status = Math.max(status, 0); (isSuccess(status) ? deferred.resolve : deferred.reject)({ data: response, status: status, headers: headersGetter(headers), config: config, statusText : statusText }); } function removePendingReq() { var idx = indexOf($http.pendingRequests, config); if (idx !== -1) $http.pendingRequests.splice(idx, 1); } } function buildUrl(url, params) { if (!params) return url; var parts = []; forEachSorted(params, function(value, key) { if (value === null || isUndefined(value)) return; if (!isArray(value)) value = [value]; forEach(value, function(v) { if (isObject(v)) { v = toJson(v); } parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(v)); }); }); if(parts.length > 0) { url += ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); } return url; } }]; } function createXhr(method) { //if IE and the method is not RFC2616 compliant, or if XMLHttpRequest //is not available, try getting an ActiveXObject. Otherwise, use XMLHttpRequest //if it is available if (msie <= 8 && (!method.match(/^(get|post|head|put|delete|options)$/i) || !window.XMLHttpRequest)) { return new window.ActiveXObject("Microsoft.XMLHTTP"); } else if (window.XMLHttpRequest) { return new window.XMLHttpRequest(); } throw minErr('$httpBackend')('noxhr', "This browser does not support XMLHttpRequest."); } /** * @ngdoc service * @name $httpBackend * @requires $window * @requires $document * * @description * HTTP backend used by the {@link ng.$http service} that delegates to * XMLHttpRequest object or JSONP and deals with browser incompatibilities. * * You should never need to use this service directly, instead use the higher-level abstractions: * {@link ng.$http $http} or {@link ngResource.$resource $resource}. * * During testing this implementation is swapped with {@link ngMock.$httpBackend mock * $httpBackend} which can be trained with responses. */ function $HttpBackendProvider() { this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) { return createHttpBackend($browser, createXhr, $browser.defer, $window.angular.callbacks, $document[0]); }]; } function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { var ABORTED = -1; // TODO(vojta): fix the signature return function(method, url, post, callback, headers, timeout, withCredentials, responseType) { var status; $browser.$$incOutstandingRequestCount(); url = url || $browser.url(); if (lowercase(method) == 'jsonp') { var callbackId = '_' + (callbacks.counter++).toString(36); callbacks[callbackId] = function(data) { callbacks[callbackId].data = data; callbacks[callbackId].called = true; }; var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), callbackId, function(status, text) { completeRequest(callback, status, callbacks[callbackId].data, "", text); callbacks[callbackId] = noop; }); } else { var xhr = createXhr(method); xhr.open(method, url, true); forEach(headers, function(value, key) { if (isDefined(value)) { xhr.setRequestHeader(key, value); } }); // In IE6 and 7, this might be called synchronously when xhr.send below is called and the // response is in the cache. the promise api will ensure that to the app code the api is // always async xhr.onreadystatechange = function() { // onreadystatechange might get called multiple times with readyState === 4 on mobile webkit caused by // xhrs that are resolved while the app is in the background (see #5426). // since calling completeRequest sets the `xhr` variable to null, we just check if it's not null before // continuing // // we can't set xhr.onreadystatechange to undefined or delete it because that breaks IE8 (method=PATCH) and // Safari respectively. if (xhr && xhr.readyState == 4) { var responseHeaders = null, response = null; if(status !== ABORTED) { responseHeaders = xhr.getAllResponseHeaders(); // responseText is the old-school way of retrieving response (supported by IE8 & 9) // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) response = ('response' in xhr) ? xhr.response : xhr.responseText; } completeRequest(callback, status || xhr.status, response, responseHeaders, xhr.statusText || ''); } }; if (withCredentials) { xhr.withCredentials = true; } if (responseType) { try { xhr.responseType = responseType; } catch (e) { // WebKit added support for the json responseType value on 09/03/2013 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are // known to throw when setting the value "json" as the response type. Other older // browsers implementing the responseType // // The json response type can be ignored if not supported, because JSON payloads are // parsed on the client-side regardless. if (responseType !== 'json') { throw e; } } } xhr.send(post || null); } if (timeout > 0) { var timeoutId = $browserDefer(timeoutRequest, timeout); } else if (timeout && timeout.then) { timeout.then(timeoutRequest); } function timeoutRequest() { status = ABORTED; jsonpDone && jsonpDone(); xhr && xhr.abort(); } function completeRequest(callback, status, response, headersString, statusText) { // cancel timeout and subsequent timeout promise resolution timeoutId && $browserDefer.cancel(timeoutId); jsonpDone = xhr = null; // fix status code when it is 0 (0 status is undocumented). // Occurs when accessing file resources or on Android 4.1 stock browser // while retrieving files from application cache. if (status === 0) { status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0; } // normalize IE bug (http://bugs.jquery.com/ticket/1450) status = status === 1223 ? 204 : status; statusText = statusText || ''; callback(status, response, headersString, statusText); $browser.$$completeOutstandingRequest(noop); } }; function jsonpReq(url, callbackId, done) { // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.: // - fetches local scripts via XHR and evals them // - adds and immediately removes script elements from the document var script = rawDocument.createElement('script'), callback = null; script.type = "text/javascript"; script.src = url; script.async = true; callback = function(event) { removeEventListenerFn(script, "load", callback); removeEventListenerFn(script, "error", callback); rawDocument.body.removeChild(script); script = null; var status = -1; var text = "unknown"; if (event) { if (event.type === "load" && !callbacks[callbackId].called) { event = { type: "error" }; } text = event.type; status = event.type === "error" ? 404 : 200; } if (done) { done(status, text); } }; addEventListenerFn(script, "load", callback); addEventListenerFn(script, "error", callback); rawDocument.body.appendChild(script); return callback; } } var $interpolateMinErr = minErr('$interpolate'); /** * @ngdoc provider * @name $interpolateProvider * @function * * @description * * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. * * @example
      //demo.label//
      it('should interpolate binding with custom symbols', function() { expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); });
      */ function $InterpolateProvider() { var startSymbol = '{{'; var endSymbol = '}}'; /** * @ngdoc method * @name $interpolateProvider#startSymbol * @description * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. * * @param {string=} value new value to set the starting symbol to. * @returns {string|self} Returns the symbol when used as getter and self if used as setter. */ this.startSymbol = function(value){ if (value) { startSymbol = value; return this; } else { return startSymbol; } }; /** * @ngdoc method * @name $interpolateProvider#endSymbol * @description * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. * * @param {string=} value new value to set the ending symbol to. * @returns {string|self} Returns the symbol when used as getter and self if used as setter. */ this.endSymbol = function(value){ if (value) { endSymbol = value; return this; } else { return endSymbol; } }; this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { var startSymbolLength = startSymbol.length, endSymbolLength = endSymbol.length, escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'), escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g'); function escape(ch) { return '\\\\\\' + ch; } /** * @ngdoc service * @name $interpolate * @function * * @requires $parse * @requires $sce * * @description * * Compiles a string with markup into an interpolation function. This service is used by the * HTML {@link ng.$compile $compile} service for data binding. See * {@link ng.$interpolateProvider $interpolateProvider} for configuring the * interpolation markup. * * * ```js * var $interpolate = ...; // injected * var exp = $interpolate('Hello {{name | uppercase}}!'); * expect(exp({name:'Angular'}).toEqual('Hello ANGULAR!'); * ``` * * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is * `true`, the interpolation function will return `undefined` unless all embedded expressions * evaluate to a value other than `undefined`. * * ```js * var $interpolate = ...; // injected * var context = {greeting: 'Hello', name: undefined }; * * // default "forgiving" mode * var exp = $interpolate('{{greeting}} {{name}}!'); * expect(exp(context)).toEqual('Hello !'); * * // "allOrNothing" mode * exp = $interpolate('{{greeting}} {{name}}!', false, null, true); * expect(exp(context, true)).toBeUndefined(); * context.name = 'Angular'; * expect(exp(context, true)).toEqual('Hello Angular!'); * ``` * * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior. * * ####Escaped Interpolation * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash). * It will be rendered as a regular start/end marker, and will not be interpreted as an expression * or binding. * * This enables web-servers to prevent script injection attacks and defacing attacks, to some * degree, while also enabling code examples to work without relying on the * {@link ng.directive:ngNonBindable ngNonBindable} directive. * * **For security purposes, it is strongly encouraged that web servers escape user-supplied data, * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all * interpolation start/end markers with their escaped counterparts.** * * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered * output when the $interpolate service processes the text. So, for HTML elements interpolated * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such, * this is typically useful only when user-data is used in rendering a template from the server, or * when otherwise untrusted data is used by a directive. * * * *
      *

      {{apptitle}}: \{\{ username = "defaced value"; \}\} *

      *

      {{username}} attempts to inject code which will deface the * application, but fails to accomplish their task, because the server has correctly * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash) * characters.

      *

      Instead, the result of the attempted script injection is visible, and can be removed * from the database by an administrator.

      *
      *
      *
      * * @param {string} text The text with markup to interpolate. * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have * embedded expression in order to return an interpolation function. Strings with no * embedded expression will return null for the interpolation function. * @param {string=} trustedContext when provided, the returned function passes the interpolated * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that * provides Strict Contextual Escaping for details. * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined * unless all embedded expressions evaluate to a value other than `undefined`. * @returns {function(context)} an interpolation function which is used to compute the * interpolated string. The function has these parameters: * * - `context`: evaluation context for all expressions embedded in the interpolated text */ function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) { allOrNothing = !!allOrNothing; var startIndex, endIndex, index = 0, separators = [], expressions = [], parseFns = [], textLength = text.length, hasInterpolation = false, hasText = false, exp, concat = [], lastValuesCache = { values: {}, results: {}}; while(index < textLength) { if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) && ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) { if (index !== startIndex) hasText = true; separators.push(text.substring(index, startIndex)); exp = text.substring(startIndex + startSymbolLength, endIndex); expressions.push(exp); parseFns.push($parse(exp)); index = endIndex + endSymbolLength; hasInterpolation = true; } else { // we did not find an interpolation, so we have to add the remainder to the separators array if (index !== textLength) { hasText = true; separators.push(text.substring(index)); } break; } } forEach(separators, function(key, i) { separators[i] = separators[i]. replace(escapedStartRegexp, startSymbol). replace(escapedEndRegexp, endSymbol); }); if (separators.length === expressions.length) { separators.push(''); } // Concatenating expressions makes it hard to reason about whether some combination of // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a // single expression be used for iframe[src], object[src], etc., we ensure that the value // that's used is assigned or constructed by some JS code somewhere that is more testable or // make it obvious that you bound the value to some user controlled value. This helps reduce // the load when auditing for XSS issues. if (trustedContext && hasInterpolation && (hasText || expressions.length > 1)) { throw $interpolateMinErr('noconcat', "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + "interpolations that concatenate multiple expressions when a trusted value is " + "required. See http://docs.angularjs.org/api/ng.$sce", text); } if (!mustHaveExpression || hasInterpolation) { concat.length = separators.length + expressions.length; var compute = function(values) { for(var i = 0, ii = expressions.length; i < ii; i++) { concat[2*i] = separators[i]; concat[(2*i)+1] = values[i]; } concat[2*ii] = separators[ii]; return concat.join(''); }; var getValue = function (value) { if (trustedContext) { value = $sce.getTrusted(trustedContext, value); } else { value = $sce.valueOf(value); } return value; }; var stringify = function (value) { if (value == null) { // null || undefined return ''; } switch (typeof value) { case 'string': { break; } case 'number': { value = '' + value; break; } default: { value = toJson(value); } } return value; }; return extend(function interpolationFn(context) { var scopeId = (context && context.$id) || 'notAScope'; var lastValues = lastValuesCache.values[scopeId]; var lastResult = lastValuesCache.results[scopeId]; var i = 0; var ii = expressions.length; var values = new Array(ii); var val; var inputsChanged = lastResult === undefined ? true: false; // if we haven't seen this context before, initialize the cache and try to setup // a cleanup routine that purges the cache when the scope goes away. if (!lastValues) { lastValues = []; inputsChanged = true; if (context && context.$on) { context.$on('$destroy', function() { lastValuesCache.values[scopeId] = null; lastValuesCache.results[scopeId] = null; }); } } try { interpolationFn.$$unwatch = true; for (; i < ii; i++) { val = getValue(parseFns[i](context)); if (allOrNothing && isUndefined(val)) { interpolationFn.$$unwatch = undefined; return; } val = stringify(val); if (val !== lastValues[i]) { inputsChanged = true; } values[i] = val; interpolationFn.$$unwatch = interpolationFn.$$unwatch && parseFns[i].$$unwatch; } if (inputsChanged) { lastValuesCache.values[scopeId] = values; lastValuesCache.results[scopeId] = lastResult = compute(values); } } catch(err) { var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString()); $exceptionHandler(newErr); } return lastResult; }, { // all of these properties are undocumented for now exp: text, //just for compatibility with regular watchers created via $watch separators: separators, expressions: expressions }); } } /** * @ngdoc method * @name $interpolate#startSymbol * @description * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. * * Use {@link ng.$interpolateProvider#startSymbol $interpolateProvider#startSymbol} to change * the symbol. * * @returns {string} start symbol. */ $interpolate.startSymbol = function() { return startSymbol; }; /** * @ngdoc method * @name $interpolate#endSymbol * @description * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. * * Use {@link ng.$interpolateProvider#endSymbol $interpolateProvider#endSymbol} to change * the symbol. * * @returns {string} end symbol. */ $interpolate.endSymbol = function() { return endSymbol; }; return $interpolate; }]; } function $IntervalProvider() { this.$get = ['$rootScope', '$window', '$q', function($rootScope, $window, $q) { var intervals = {}; /** * @ngdoc service * @name $interval * * @description * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` * milliseconds. * * The return value of registering an interval function is a promise. This promise will be * notified upon each tick of the interval, and will be resolved after `count` iterations, or * run indefinitely if `count` is not defined. The value of the notification will be the * number of iterations that have run. * To cancel an interval, call `$interval.cancel(promise)`. * * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to * move forward by `millis` milliseconds and trigger any functions scheduled to run in that * time. * *
      * **Note**: Intervals created by this service must be explicitly destroyed when you are finished * with them. In particular they are not automatically destroyed when a controller's scope or a * directive's element are destroyed. * You should take this into consideration and make sure to always cancel the interval at the * appropriate moment. See the example below for more details on how and when to do this. *
      * * @param {function()} fn A function that should be called repeatedly. * @param {number} delay Number of milliseconds between each function call. * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat * indefinitely. * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. * @returns {promise} A promise which will be notified on each iteration. * * @example * * * * *
      *
      * Date format:
      * Current time is: *
      * Blood 1 : {{blood_1}} * Blood 2 : {{blood_2}} * * * *
      *
      * *
      *
      */ function interval(fn, delay, count, invokeApply) { var setInterval = $window.setInterval, clearInterval = $window.clearInterval, deferred = $q.defer(), promise = deferred.promise, iteration = 0, skipApply = (isDefined(invokeApply) && !invokeApply); count = isDefined(count) ? count : 0; promise.then(null, null, fn); promise.$$intervalId = setInterval(function tick() { deferred.notify(iteration++); if (count > 0 && iteration >= count) { deferred.resolve(iteration); clearInterval(promise.$$intervalId); delete intervals[promise.$$intervalId]; } if (!skipApply) $rootScope.$apply(); }, delay); intervals[promise.$$intervalId] = deferred; return promise; } /** * @ngdoc method * @name $interval#cancel * * @description * Cancels a task associated with the `promise`. * * @param {promise} promise returned by the `$interval` function. * @returns {boolean} Returns `true` if the task was successfully canceled. */ interval.cancel = function(promise) { if (promise && promise.$$intervalId in intervals) { intervals[promise.$$intervalId].reject('canceled'); clearInterval(promise.$$intervalId); delete intervals[promise.$$intervalId]; return true; } return false; }; return interval; }]; } /** * @ngdoc service * @name $locale * * @description * $locale service provides localization rules for various Angular components. As of right now the * only public api is: * * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`) */ function $LocaleProvider(){ this.$get = function() { return { id: 'en-us', NUMBER_FORMATS: { DECIMAL_SEP: '.', GROUP_SEP: ',', PATTERNS: [ { // Decimal Pattern minInt: 1, minFrac: 0, maxFrac: 3, posPre: '', posSuf: '', negPre: '-', negSuf: '', gSize: 3, lgSize: 3 },{ //Currency Pattern minInt: 1, minFrac: 2, maxFrac: 2, posPre: '\u00A4', posSuf: '', negPre: '(\u00A4', negSuf: ')', gSize: 3, lgSize: 3 } ], CURRENCY_SYM: '$' }, DATETIME_FORMATS: { MONTH: 'January,February,March,April,May,June,July,August,September,October,November,December' .split(','), SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','), DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','), SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','), AMPMS: ['AM','PM'], medium: 'MMM d, y h:mm:ss a', short: 'M/d/yy h:mm a', fullDate: 'EEEE, MMMM d, y', longDate: 'MMMM d, y', mediumDate: 'MMM d, y', shortDate: 'M/d/yy', mediumTime: 'h:mm:ss a', shortTime: 'h:mm a' }, pluralCat: function(num) { if (num === 1) { return 'one'; } return 'other'; } }; }; } var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; var $locationMinErr = minErr('$location'); /** * Encode path using encodeUriSegment, ignoring forward slashes * * @param {string} path Path to encode * @returns {string} */ function encodePath(path) { var segments = path.split('/'), i = segments.length; while (i--) { segments[i] = encodeUriSegment(segments[i]); } return segments.join('/'); } function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) { var parsedUrl = urlResolve(absoluteUrl, appBase); locationObj.$$protocol = parsedUrl.protocol; locationObj.$$host = parsedUrl.hostname; locationObj.$$port = int(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; } function parseAppUrl(relativeUrl, locationObj, appBase) { var prefixed = (relativeUrl.charAt(0) !== '/'); if (prefixed) { relativeUrl = '/' + relativeUrl; } var match = urlResolve(relativeUrl, appBase); locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? match.pathname.substring(1) : match.pathname); locationObj.$$search = parseKeyValue(match.search); locationObj.$$hash = decodeURIComponent(match.hash); // make sure path starts with '/'; if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') { locationObj.$$path = '/' + locationObj.$$path; } } /** * * @param {string} begin * @param {string} whole * @returns {string} returns text from whole after begin or undefined if it does not begin with * expected string. */ function beginsWith(begin, whole) { if (whole.indexOf(begin) === 0) { return whole.substr(begin.length); } } function stripHash(url) { var index = url.indexOf('#'); return index == -1 ? url : url.substr(0, index); } function stripFile(url) { return url.substr(0, stripHash(url).lastIndexOf('/') + 1); } /* return the server only (scheme://host:port) */ function serverBase(url) { return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); } /** * LocationHtml5Url represents an url * This object is exposed as $location service when HTML5 mode is enabled and supported * * @constructor * @param {string} appBase application base URL * @param {string} basePrefix url path prefix */ function LocationHtml5Url(appBase, basePrefix) { this.$$html5 = true; basePrefix = basePrefix || ''; var appBaseNoFile = stripFile(appBase); parseAbsoluteUrl(appBase, this, appBase); /** * Parse given html5 (regular) url string into properties * @param {string} newAbsoluteUrl HTML5 url * @private */ this.$$parse = function(url) { var pathUrl = beginsWith(appBaseNoFile, url); if (!isString(pathUrl)) { throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, appBaseNoFile); } parseAppUrl(pathUrl, this, appBase); if (!this.$$path) { this.$$path = '/'; } this.$$compose(); }; /** * Compose url and update `absUrl` property * @private */ this.$$compose = function() { var search = toKeyValue(this.$$search), hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' }; this.$$rewrite = function(url) { var appUrl, prevAppUrl; if ( (appUrl = beginsWith(appBase, url)) !== undefined ) { prevAppUrl = appUrl; if ( (appUrl = beginsWith(basePrefix, appUrl)) !== undefined ) { return appBaseNoFile + (beginsWith('/', appUrl) || appUrl); } else { return appBase + prevAppUrl; } } else if ( (appUrl = beginsWith(appBaseNoFile, url)) !== undefined ) { return appBaseNoFile + appUrl; } else if (appBaseNoFile == url + '/') { return appBaseNoFile; } }; } /** * LocationHashbangUrl represents url * This object is exposed as $location service when developer doesn't opt into html5 mode. * It also serves as the base class for html5 mode fallback on legacy browsers. * * @constructor * @param {string} appBase application base URL * @param {string} hashPrefix hashbang prefix */ function LocationHashbangUrl(appBase, hashPrefix) { var appBaseNoFile = stripFile(appBase); parseAbsoluteUrl(appBase, this, appBase); /** * Parse given hashbang url into properties * @param {string} url Hashbang url * @private */ this.$$parse = function(url) { var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); var withoutHashUrl = withoutBaseUrl.charAt(0) == '#' ? beginsWith(hashPrefix, withoutBaseUrl) : (this.$$html5) ? withoutBaseUrl : ''; if (!isString(withoutHashUrl)) { throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url, hashPrefix); } parseAppUrl(withoutHashUrl, this, appBase); this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); this.$$compose(); /* * In Windows, on an anchor node on documents loaded from * the filesystem, the browser will return a pathname * prefixed with the drive name ('/C:/path') when a * pathname without a drive is set: * * a.setAttribute('href', '/foo') * * a.pathname === '/C:/foo' //true * * Inside of Angular, we're always using pathnames that * do not include drive names for routing. */ function removeWindowsDriveName (path, url, base) { /* Matches paths for file protocol on windows, such as /C:/foo/bar, and captures only /foo/bar. */ var windowsFilePathExp = /^\/[A-Z]:(\/.*)/; var firstPathSegmentMatch; //Get the relative path from the input URL. if (url.indexOf(base) === 0) { url = url.replace(base, ''); } // The input URL intentionally contains a first path segment that ends with a colon. if (windowsFilePathExp.exec(url)) { return path; } firstPathSegmentMatch = windowsFilePathExp.exec(path); return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; } }; /** * Compose hashbang url and update `absUrl` property * @private */ this.$$compose = function() { var search = toKeyValue(this.$$search), hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); }; this.$$rewrite = function(url) { if(stripHash(appBase) == stripHash(url)) { return url; } }; } /** * LocationHashbangUrl represents url * This object is exposed as $location service when html5 history api is enabled but the browser * does not support it. * * @constructor * @param {string} appBase application base URL * @param {string} hashPrefix hashbang prefix */ function LocationHashbangInHtml5Url(appBase, hashPrefix) { this.$$html5 = true; LocationHashbangUrl.apply(this, arguments); var appBaseNoFile = stripFile(appBase); this.$$rewrite = function(url) { var appUrl; if ( appBase == stripHash(url) ) { return url; } else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) { return appBase + hashPrefix + appUrl; } else if ( appBaseNoFile === url + '/') { return appBaseNoFile; } }; this.$$compose = function() { var search = toKeyValue(this.$$search), hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; // include hashPrefix in $$absUrl when $$url is empty so IE8 & 9 do not reload page because of removal of '#' this.$$absUrl = appBase + hashPrefix + this.$$url; }; } LocationHashbangInHtml5Url.prototype = LocationHashbangUrl.prototype = LocationHtml5Url.prototype = { /** * Are we in html5 mode? * @private */ $$html5: false, /** * Has any change been replacing ? * @private */ $$replace: false, /** * @ngdoc method * @name $location#absUrl * * @description * This method is getter only. * * Return full url representation with all segments encoded according to rules specified in * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). * * @return {string} full url */ absUrl: locationGetter('$$absUrl'), /** * @ngdoc method * @name $location#url * * @description * This method is getter / setter. * * Return url (e.g. `/path?a=b#hash`) when called without any parameter. * * Change path, search and hash, when called with parameter and return `$location`. * * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) * @param {string=} replace The path that will be changed * @return {string} url */ url: function(url, replace) { if (isUndefined(url)) return this.$$url; var match = PATH_MATCH.exec(url); if (match[1]) this.path(decodeURIComponent(match[1])); if (match[2] || match[1]) this.search(match[3] || ''); this.hash(match[5] || '', replace); return this; }, /** * @ngdoc method * @name $location#protocol * * @description * This method is getter only. * * Return protocol of current url. * * @return {string} protocol of current url */ protocol: locationGetter('$$protocol'), /** * @ngdoc method * @name $location#host * * @description * This method is getter only. * * Return host of current url. * * @return {string} host of current url. */ host: locationGetter('$$host'), /** * @ngdoc method * @name $location#port * * @description * This method is getter only. * * Return port of current url. * * @return {Number} port */ port: locationGetter('$$port'), /** * @ngdoc method * @name $location#path * * @description * This method is getter / setter. * * Return path of current url when called without any parameter. * * Change path when called with parameter and return `$location`. * * Note: Path should always begin with forward slash (/), this method will add the forward slash * if it is missing. * * @param {string=} path New path * @return {string} path */ path: locationGetterSetter('$$path', function(path) { return path.charAt(0) == '/' ? path : '/' + path; }), /** * @ngdoc method * @name $location#search * * @description * This method is getter / setter. * * Return search part (as object) of current url when called without any parameter. * * Change search part when called with parameter and return `$location`. * * * ```js * // given url http://example.com/#/some/path?foo=bar&baz=xoxo * var searchObject = $location.search(); * // => {foo: 'bar', baz: 'xoxo'} * * * // set foo to 'yipee' * $location.search('foo', 'yipee'); * // => $location * ``` * * @param {string|Object.|Object.>} search New search params - string or * hash object. * * When called with a single argument the method acts as a setter, setting the `search` component * of `$location` to the specified value. * * If the argument is a hash object containing an array of values, these values will be encoded * as duplicate search parameters in the url. * * @param {(string|Array)=} paramValue If `search` is a string, then `paramValue` will * override only a single search property. * * If `paramValue` is an array, it will override the property of the `search` component of * `$location` specified via the first argument. * * If `paramValue` is `null`, the property specified via the first argument will be deleted. * * @return {Object} If called with no arguments returns the parsed `search` object. If called with * one or more arguments returns `$location` object itself. */ search: function(search, paramValue) { switch (arguments.length) { case 0: return this.$$search; case 1: if (isString(search)) { this.$$search = parseKeyValue(search); } else if (isObject(search)) { this.$$search = search; } else { throw $locationMinErr('isrcharg', 'The first argument of the `$location#search()` call must be a string or an object.'); } break; default: if (isUndefined(paramValue) || paramValue === null) { delete this.$$search[search]; } else { this.$$search[search] = paramValue; } } this.$$compose(); return this; }, /** * @ngdoc method * @name $location#hash * * @description * This method is getter / setter. * * Return hash fragment when called without any parameter. * * Change hash fragment when called with parameter and return `$location`. * * @param {string=} hash New hash fragment * @return {string} hash */ hash: locationGetterSetter('$$hash', identity), /** * @ngdoc method * @name $location#replace * * @description * If called, all changes to $location during current `$digest` will be replacing current history * record, instead of adding new one. */ replace: function() { this.$$replace = true; return this; } }; function locationGetter(property) { return function() { return this[property]; }; } function locationGetterSetter(property, preprocess) { return function(value) { if (isUndefined(value)) return this[property]; this[property] = preprocess(value); this.$$compose(); return this; }; } /** * @ngdoc service * @name $location * * @requires $rootElement * * @description * The $location service parses the URL in the browser address bar (based on the * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL * available to your application. Changes to the URL in the address bar are reflected into * $location service and changes to $location are reflected into the browser address bar. * * **The $location service:** * * - Exposes the current URL in the browser address bar, so you can * - Watch and observe the URL. * - Change the URL. * - Synchronizes the URL with the browser when the user * - Changes the address bar. * - Clicks the back or forward button (or clicks a History link). * - Clicks on a link. * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). * * For more information see {@link guide/$location Developer Guide: Using $location} */ /** * @ngdoc provider * @name $locationProvider * @description * Use the `$locationProvider` to configure how the application deep linking paths are stored. */ function $LocationProvider(){ var hashPrefix = '', html5Mode = false; /** * @ngdoc property * @name $locationProvider#hashPrefix * @description * @param {string=} prefix Prefix for hash part (containing path and search) * @returns {*} current value if used as getter or itself (chaining) if used as setter */ this.hashPrefix = function(prefix) { if (isDefined(prefix)) { hashPrefix = prefix; return this; } else { return hashPrefix; } }; /** * @ngdoc property * @name $locationProvider#html5Mode * @description * @param {boolean=} mode Use HTML5 strategy if available. * @returns {*} current value if used as getter or itself (chaining) if used as setter */ this.html5Mode = function(mode) { if (isDefined(mode)) { html5Mode = mode; return this; } else { return html5Mode; } }; /** * @ngdoc event * @name $location#$locationChangeStart * @eventType broadcast on root scope * @description * Broadcasted before a URL will change. This change can be prevented by calling * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more * details about event object. Upon successful change * {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired. * * @param {Object} angularEvent Synthetic event object. * @param {string} newUrl New URL * @param {string=} oldUrl URL that was before it was changed. */ /** * @ngdoc event * @name $location#$locationChangeSuccess * @eventType broadcast on root scope * @description * Broadcasted after a URL was changed. * * @param {Object} angularEvent Synthetic event object. * @param {string} newUrl New URL * @param {string=} oldUrl URL that was before it was changed. */ this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', function( $rootScope, $browser, $sniffer, $rootElement) { var $location, LocationMode, baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' initialUrl = $browser.url(), appBase; if (html5Mode) { appBase = serverBase(initialUrl) + (baseHref || '/'); LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; } else { appBase = stripHash(initialUrl); LocationMode = LocationHashbangUrl; } $location = new LocationMode(appBase, '#' + hashPrefix); $location.$$parse($location.$$rewrite(initialUrl)); $rootElement.on('click', function(event) { // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) // currently we open nice url link and redirect then if (event.ctrlKey || event.metaKey || event.which == 2) return; var elm = jqLite(event.target); // traverse the DOM up to find first A tag while (lowercase(elm[0].nodeName) !== 'a') { // ignore rewriting if no A tag (reached root element, or no parent - removed from document) if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; } var absHref = elm.prop('href'); if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during // an animation. absHref = urlResolve(absHref.animVal).href; } // Make relative links work in HTML5 mode for legacy browsers (or at least IE8 & 9) // The href should be a regular url e.g. /link/somewhere or link/somewhere or ../somewhere or // somewhere#anchor or http://example.com/somewhere if (LocationMode === LocationHashbangInHtml5Url) { // get the actual href attribute - see // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx var href = elm.attr('href') || elm.attr('xlink:href'); if (href.indexOf('://') < 0) { // Ignore absolute URLs var prefix = '#' + hashPrefix; if (href[0] == '/') { // absolute path - replace old path absHref = appBase + prefix + href; } else if (href[0] == '#') { // local anchor absHref = appBase + prefix + ($location.path() || '/') + href; } else { // relative path - join with current path var stack = $location.path().split("/"), parts = href.split("/"); for (var i=0; i html5 url if ($location.absUrl() != initialUrl) { $browser.url($location.absUrl(), true); } // update $location when $browser url changes $browser.onUrlChange(function(newUrl) { if ($location.absUrl() != newUrl) { $rootScope.$evalAsync(function() { var oldUrl = $location.absUrl(); $location.$$parse(newUrl); if ($rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl).defaultPrevented) { $location.$$parse(oldUrl); $browser.url(oldUrl); } else { afterLocationChange(oldUrl); } }); if (!$rootScope.$$phase) $rootScope.$digest(); } }); // update browser var changeCounter = 0; $rootScope.$watch(function $locationWatch() { var oldUrl = $browser.url(); var currentReplace = $location.$$replace; if (!changeCounter || oldUrl != $location.absUrl()) { changeCounter++; $rootScope.$evalAsync(function() { if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl). defaultPrevented) { $location.$$parse(oldUrl); } else { $browser.url($location.absUrl(), currentReplace); afterLocationChange(oldUrl); } }); } $location.$$replace = false; return changeCounter; }); return $location; function afterLocationChange(oldUrl) { $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl); } }]; } /** * @ngdoc service * @name $log * @requires $window * * @description * Simple service for logging. Default implementation safely writes the message * into the browser's console (if present). * * The main purpose of this service is to simplify debugging and troubleshooting. * * The default is to log `debug` messages. You can use * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. * * @example function LogCtrl($scope, $log) { $scope.$log = $log; $scope.message = 'Hello World!'; }

      Reload this page with open console, enter text and hit the log button...

      Message:
      */ /** * @ngdoc provider * @name $logProvider * @description * Use the `$logProvider` to configure how the application logs messages */ function $LogProvider(){ var debug = true, self = this; /** * @ngdoc property * @name $logProvider#debugEnabled * @description * @param {boolean=} flag enable or disable debug level messages * @returns {*} current value if used as getter or itself (chaining) if used as setter */ this.debugEnabled = function(flag) { if (isDefined(flag)) { debug = flag; return this; } else { return debug; } }; this.$get = ['$window', function($window){ return { /** * @ngdoc method * @name $log#log * * @description * Write a log message */ log: consoleLog('log'), /** * @ngdoc method * @name $log#info * * @description * Write an information message */ info: consoleLog('info'), /** * @ngdoc method * @name $log#warn * * @description * Write a warning message */ warn: consoleLog('warn'), /** * @ngdoc method * @name $log#error * * @description * Write an error message */ error: consoleLog('error'), /** * @ngdoc method * @name $log#debug * * @description * Write a debug message */ debug: (function () { var fn = consoleLog('debug'); return function() { if (debug) { fn.apply(self, arguments); } }; }()) }; function formatError(arg) { if (arg instanceof Error) { if (arg.stack) { arg = (arg.message && arg.stack.indexOf(arg.message) === -1) ? 'Error: ' + arg.message + '\n' + arg.stack : arg.stack; } else if (arg.sourceURL) { arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; } } return arg; } function consoleLog(type) { var console = $window.console || {}, logFn = console[type] || console.log || noop, hasApply = false; // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. // The reason behind this is that console.log has type "object" in IE8... try { hasApply = !!logFn.apply; } catch (e) {} if (hasApply) { return function() { var args = []; forEach(arguments, function(arg) { args.push(formatError(arg)); }); return logFn.apply(console, args); }; } // we are IE which either doesn't have window.console => this is noop and we do nothing, // or we are IE where console.log doesn't have apply so we log at least first 2 args return function(arg1, arg2) { logFn(arg1, arg2 == null ? '' : arg2); }; } }]; } var $parseMinErr = minErr('$parse'); // Sandboxing Angular Expressions // ------------------------------ // Angular expressions are generally considered safe because these expressions only have direct // access to $scope and locals. However, one can obtain the ability to execute arbitrary JS code by // obtaining a reference to native JS functions such as the Function constructor. // // As an example, consider the following Angular expression: // // {}.toString.constructor(alert("evil JS code")) // // We want to prevent this type of access. For the sake of performance, during the lexing phase we // disallow any "dotted" access to any member named "constructor". // // For reflective calls (a[b]) we check that the value of the lookup is not the Function constructor // while evaluating the expression, which is a stronger but more expensive test. Since reflective // calls are expensive anyway, this is not such a big deal compared to static dereferencing. // // This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits // against the expression language, but not to prevent exploits that were enabled by exposing // sensitive JavaScript or browser apis on Scope. Exposing such objects on a Scope is never a good // practice and therefore we are not even trying to protect against interaction with an object // explicitly exposed in this way. // // A developer could foil the name check by aliasing the Function constructor under a different // name on the scope. // // In general, it is not possible to access a Window object from an angular expression unless a // window or some DOM object that has a reference to window is published onto a Scope. function ensureSafeMemberName(name, fullExpression) { if (name === "constructor") { throw $parseMinErr('isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! Expression: {0}', fullExpression); } return name; } function ensureSafeObject(obj, fullExpression) { // nifty check if obj is Function that is fast and works across iframes and other contexts if (obj) { if (obj.constructor === obj) { throw $parseMinErr('isecfn', 'Referencing Function in Angular expressions is disallowed! Expression: {0}', fullExpression); } else if (// isWindow(obj) obj.document && obj.location && obj.alert && obj.setInterval) { throw $parseMinErr('isecwindow', 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', fullExpression); } else if (// isElement(obj) obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) { throw $parseMinErr('isecdom', 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', fullExpression); } } return obj; } var OPERATORS = { /* jshint bitwise : false */ 'null':function(){return null;}, 'true':function(){return true;}, 'false':function(){return false;}, undefined:noop, '+':function(self, locals, a,b){ a=a(self, locals); b=b(self, locals); if (isDefined(a)) { if (isDefined(b)) { return a + b; } return a; } return isDefined(b)?b:undefined;}, '-':function(self, locals, a,b){ a=a(self, locals); b=b(self, locals); return (isDefined(a)?a:0)-(isDefined(b)?b:0); }, '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);}, '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);}, '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);}, '=':noop, '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);}, '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);}, '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);}, '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);}, '<':function(self, locals, a,b){return a(self, locals)':function(self, locals, a,b){return a(self, locals)>b(self, locals);}, '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);}, '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);}, '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);}, '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);}, '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);}, // '|':function(self, locals, a,b){return a|b;}, '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));}, '!':function(self, locals, a){return !a(self, locals);} }; /* jshint bitwise: true */ var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; ///////////////////////////////////////// /** * @constructor */ var Lexer = function (options) { this.options = options; }; Lexer.prototype = { constructor: Lexer, lex: function (text) { this.text = text; this.index = 0; this.ch = undefined; this.lastCh = ':'; // can start regexp this.tokens = []; while (this.index < this.text.length) { this.ch = this.text.charAt(this.index); if (this.is('"\'')) { this.readString(this.ch); } else if (this.isNumber(this.ch) || this.is('.') && this.isNumber(this.peek())) { this.readNumber(); } else if (this.isIdent(this.ch)) { this.readIdent(); } else if (this.is('(){}[].,;:?')) { this.tokens.push({ index: this.index, text: this.ch }); this.index++; } else if (this.isWhitespace(this.ch)) { this.index++; continue; } else { var ch2 = this.ch + this.peek(); var ch3 = ch2 + this.peek(2); var fn = OPERATORS[this.ch]; var fn2 = OPERATORS[ch2]; var fn3 = OPERATORS[ch3]; if (fn3) { this.tokens.push({index: this.index, text: ch3, fn: fn3}); this.index += 3; } else if (fn2) { this.tokens.push({index: this.index, text: ch2, fn: fn2}); this.index += 2; } else if (fn) { this.tokens.push({ index: this.index, text: this.ch, fn: fn }); this.index += 1; } else { this.throwError('Unexpected next character ', this.index, this.index + 1); } } this.lastCh = this.ch; } return this.tokens; }, is: function(chars) { return chars.indexOf(this.ch) !== -1; }, was: function(chars) { return chars.indexOf(this.lastCh) !== -1; }, peek: function(i) { var num = i || 1; return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; }, isNumber: function(ch) { return ('0' <= ch && ch <= '9'); }, isWhitespace: function(ch) { // IE treats non-breaking space as \u00A0 return (ch === ' ' || ch === '\r' || ch === '\t' || ch === '\n' || ch === '\v' || ch === '\u00A0'); }, isIdent: function(ch) { return ('a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || '_' === ch || ch === '$'); }, isExpOperator: function(ch) { return (ch === '-' || ch === '+' || this.isNumber(ch)); }, throwError: function(error, start, end) { end = end || this.index; var colStr = (isDefined(start) ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' : ' ' + end); throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', error, colStr, this.text); }, readNumber: function() { var number = ''; var start = this.index; while (this.index < this.text.length) { var ch = lowercase(this.text.charAt(this.index)); if (ch == '.' || this.isNumber(ch)) { number += ch; } else { var peekCh = this.peek(); if (ch == 'e' && this.isExpOperator(peekCh)) { number += ch; } else if (this.isExpOperator(ch) && peekCh && this.isNumber(peekCh) && number.charAt(number.length - 1) == 'e') { number += ch; } else if (this.isExpOperator(ch) && (!peekCh || !this.isNumber(peekCh)) && number.charAt(number.length - 1) == 'e') { this.throwError('Invalid exponent'); } else { break; } } this.index++; } number = 1 * number; this.tokens.push({ index: start, text: number, literal: true, constant: true, fn: function() { return number; } }); }, readIdent: function() { var parser = this; var ident = ''; var start = this.index; var lastDot, peekIndex, methodName, ch; while (this.index < this.text.length) { ch = this.text.charAt(this.index); if (ch === '.' || this.isIdent(ch) || this.isNumber(ch)) { if (ch === '.') lastDot = this.index; ident += ch; } else { break; } this.index++; } //check if this is not a method invocation and if it is back out to last dot if (lastDot) { peekIndex = this.index; while (peekIndex < this.text.length) { ch = this.text.charAt(peekIndex); if (ch === '(') { methodName = ident.substr(lastDot - start + 1); ident = ident.substr(0, lastDot - start); this.index = peekIndex; break; } if (this.isWhitespace(ch)) { peekIndex++; } else { break; } } } var token = { index: start, text: ident }; // OPERATORS is our own object so we don't need to use special hasOwnPropertyFn if (OPERATORS.hasOwnProperty(ident)) { token.fn = OPERATORS[ident]; token.literal = true; token.constant = true; } else { var getter = getterFn(ident, this.options, this.text); token.fn = extend(function(self, locals) { return (getter(self, locals)); }, { assign: function(self, value) { return setter(self, ident, value, parser.text, parser.options); } }); } this.tokens.push(token); if (methodName) { this.tokens.push({ index:lastDot, text: '.' }); this.tokens.push({ index: lastDot + 1, text: methodName }); } }, readString: function(quote) { var start = this.index; this.index++; var string = ''; var rawString = quote; var escape = false; while (this.index < this.text.length) { var ch = this.text.charAt(this.index); rawString += ch; if (escape) { if (ch === 'u') { var hex = this.text.substring(this.index + 1, this.index + 5); if (!hex.match(/[\da-f]{4}/i)) this.throwError('Invalid unicode escape [\\u' + hex + ']'); this.index += 4; string += String.fromCharCode(parseInt(hex, 16)); } else { var rep = ESCAPE[ch]; if (rep) { string += rep; } else { string += ch; } } escape = false; } else if (ch === '\\') { escape = true; } else if (ch === quote) { this.index++; this.tokens.push({ index: start, text: rawString, string: string, literal: true, constant: true, fn: function() { return string; } }); return; } else { string += ch; } this.index++; } this.throwError('Unterminated quote', start); } }; /** * @constructor */ var Parser = function (lexer, $filter, options) { this.lexer = lexer; this.$filter = $filter; this.options = options; }; Parser.ZERO = extend(function () { return 0; }, { constant: true }); Parser.prototype = { constructor: Parser, parse: function (text) { this.text = text; this.tokens = this.lexer.lex(text); var value = this.statements(); if (this.tokens.length !== 0) { this.throwError('is an unexpected token', this.tokens[0]); } value.literal = !!value.literal; value.constant = !!value.constant; return value; }, primary: function () { var primary; if (this.expect('(')) { primary = this.filterChain(); this.consume(')'); } else if (this.expect('[')) { primary = this.arrayDeclaration(); } else if (this.expect('{')) { primary = this.object(); } else { var token = this.expect(); primary = token.fn; if (!primary) { this.throwError('not a primary expression', token); } primary.literal = !!token.literal; primary.constant = !!token.constant; } var next, context; while ((next = this.expect('(', '[', '.'))) { if (next.text === '(') { primary = this.functionCall(primary, context); context = null; } else if (next.text === '[') { context = primary; primary = this.objectIndex(primary); } else if (next.text === '.') { context = primary; primary = this.fieldAccess(primary); } else { this.throwError('IMPOSSIBLE'); } } return primary; }, throwError: function(msg, token) { throw $parseMinErr('syntax', 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); }, peekToken: function() { if (this.tokens.length === 0) throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); return this.tokens[0]; }, peek: function(e1, e2, e3, e4) { if (this.tokens.length > 0) { var token = this.tokens[0]; var t = token.text; if (t === e1 || t === e2 || t === e3 || t === e4 || (!e1 && !e2 && !e3 && !e4)) { return token; } } return false; }, expect: function(e1, e2, e3, e4){ var token = this.peek(e1, e2, e3, e4); if (token) { this.tokens.shift(); return token; } return false; }, consume: function(e1){ if (!this.expect(e1)) { this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); } }, unaryFn: function(fn, right) { return extend(function(self, locals) { return fn(self, locals, right); }, { constant:right.constant }); }, ternaryFn: function(left, middle, right){ return extend(function(self, locals){ return left(self, locals) ? middle(self, locals) : right(self, locals); }, { constant: left.constant && middle.constant && right.constant }); }, binaryFn: function(left, fn, right) { return extend(function(self, locals) { return fn(self, locals, left, right); }, { constant:left.constant && right.constant }); }, statements: function() { var statements = []; while (true) { if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) statements.push(this.filterChain()); if (!this.expect(';')) { // optimize for the common case where there is only one statement. // TODO(size): maybe we should not support multiple statements? return (statements.length === 1) ? statements[0] : function(self, locals) { var value; for (var i = 0; i < statements.length; i++) { var statement = statements[i]; if (statement) { value = statement(self, locals); } } return value; }; } } }, filterChain: function() { var left = this.expression(); var token; while (true) { if ((token = this.expect('|'))) { left = this.binaryFn(left, token.fn, this.filter()); } else { return left; } } }, filter: function() { var token = this.expect(); var fn = this.$filter(token.text); var argsFn = []; while (true) { if ((token = this.expect(':'))) { argsFn.push(this.expression()); } else { var fnInvoke = function(self, locals, input) { var args = [input]; for (var i = 0; i < argsFn.length; i++) { args.push(argsFn[i](self, locals)); } return fn.apply(self, args); }; return function() { return fnInvoke; }; } } }, expression: function() { return this.assignment(); }, assignment: function() { var left = this.ternary(); var right; var token; if ((token = this.expect('='))) { if (!left.assign) { this.throwError('implies assignment but [' + this.text.substring(0, token.index) + '] can not be assigned to', token); } right = this.ternary(); return function(scope, locals) { return left.assign(scope, right(scope, locals), locals); }; } return left; }, ternary: function() { var left = this.logicalOR(); var middle; var token; if ((token = this.expect('?'))) { middle = this.ternary(); if ((token = this.expect(':'))) { return this.ternaryFn(left, middle, this.ternary()); } else { this.throwError('expected :', token); } } else { return left; } }, logicalOR: function() { var left = this.logicalAND(); var token; while (true) { if ((token = this.expect('||'))) { left = this.binaryFn(left, token.fn, this.logicalAND()); } else { return left; } } }, logicalAND: function() { var left = this.equality(); var token; if ((token = this.expect('&&'))) { left = this.binaryFn(left, token.fn, this.logicalAND()); } return left; }, equality: function() { var left = this.relational(); var token; if ((token = this.expect('==','!=','===','!=='))) { left = this.binaryFn(left, token.fn, this.equality()); } return left; }, relational: function() { var left = this.additive(); var token; if ((token = this.expect('<', '>', '<=', '>='))) { left = this.binaryFn(left, token.fn, this.relational()); } return left; }, additive: function() { var left = this.multiplicative(); var token; while ((token = this.expect('+','-'))) { left = this.binaryFn(left, token.fn, this.multiplicative()); } return left; }, multiplicative: function() { var left = this.unary(); var token; while ((token = this.expect('*','/','%'))) { left = this.binaryFn(left, token.fn, this.unary()); } return left; }, unary: function() { var token; if (this.expect('+')) { return this.primary(); } else if ((token = this.expect('-'))) { return this.binaryFn(Parser.ZERO, token.fn, this.unary()); } else if ((token = this.expect('!'))) { return this.unaryFn(token.fn, this.unary()); } else { return this.primary(); } }, fieldAccess: function(object) { var parser = this; var field = this.expect().text; var getter = getterFn(field, this.options, this.text); return extend(function(scope, locals, self) { return getter(self || object(scope, locals)); }, { assign: function(scope, value, locals) { return setter(object(scope, locals), field, value, parser.text, parser.options); } }); }, objectIndex: function(obj) { var parser = this; var indexFn = this.expression(); this.consume(']'); return extend(function(self, locals) { var o = obj(self, locals), i = indexFn(self, locals), v, p; if (!o) return undefined; v = ensureSafeObject(o[i], parser.text); return v; }, { assign: function(self, value, locals) { var key = indexFn(self, locals); // prevent overwriting of Function.constructor which would break ensureSafeObject check var safe = ensureSafeObject(obj(self, locals), parser.text); return safe[key] = value; } }); }, functionCall: function(fn, contextGetter) { var argsFn = []; if (this.peekToken().text !== ')') { do { argsFn.push(this.expression()); } while (this.expect(',')); } this.consume(')'); var parser = this; return function(scope, locals) { var args = []; var context = contextGetter ? contextGetter(scope, locals) : scope; for (var i = 0; i < argsFn.length; i++) { args.push(argsFn[i](scope, locals)); } var fnPtr = fn(scope, locals, context) || noop; ensureSafeObject(context, parser.text); ensureSafeObject(fnPtr, parser.text); // IE stupidity! (IE doesn't have apply for some native functions) var v = fnPtr.apply ? fnPtr.apply(context, args) : fnPtr(args[0], args[1], args[2], args[3], args[4]); return ensureSafeObject(v, parser.text); }; }, // This is used with json array declaration arrayDeclaration: function () { var elementFns = []; var allConstant = true; if (this.peekToken().text !== ']') { do { if (this.peek(']')) { // Support trailing commas per ES5.1. break; } var elementFn = this.expression(); elementFns.push(elementFn); if (!elementFn.constant) { allConstant = false; } } while (this.expect(',')); } this.consume(']'); return extend(function(self, locals) { var array = []; for (var i = 0; i < elementFns.length; i++) { array.push(elementFns[i](self, locals)); } return array; }, { literal: true, constant: allConstant }); }, object: function () { var keyValues = []; var allConstant = true; if (this.peekToken().text !== '}') { do { if (this.peek('}')) { // Support trailing commas per ES5.1. break; } var token = this.expect(), key = token.string || token.text; this.consume(':'); var value = this.expression(); keyValues.push({key: key, value: value}); if (!value.constant) { allConstant = false; } } while (this.expect(',')); } this.consume('}'); return extend(function(self, locals) { var object = {}; for (var i = 0; i < keyValues.length; i++) { var keyValue = keyValues[i]; object[keyValue.key] = keyValue.value(self, locals); } return object; }, { literal: true, constant: allConstant }); } }; ////////////////////////////////////////////////// // Parser helper functions ////////////////////////////////////////////////// function setter(obj, path, setValue, fullExp, options) { //needed? options = options || {}; var element = path.split('.'), key; for (var i = 0; element.length > 1; i++) { key = ensureSafeMemberName(element.shift(), fullExp); var propertyObj = obj[key]; if (!propertyObj) { propertyObj = {}; obj[key] = propertyObj; } obj = propertyObj; } key = ensureSafeMemberName(element.shift(), fullExp); obj[key] = setValue; return setValue; } var getterFnCache = {}; /** * Implementation of the "Black Hole" variant from: * - http://jsperf.com/angularjs-parse-getter/4 * - http://jsperf.com/path-evaluation-simplified/7 */ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) { ensureSafeMemberName(key0, fullExp); ensureSafeMemberName(key1, fullExp); ensureSafeMemberName(key2, fullExp); ensureSafeMemberName(key3, fullExp); ensureSafeMemberName(key4, fullExp); return function cspSafeGetter(scope, locals) { var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope; if (pathVal == null) return pathVal; pathVal = pathVal[key0]; if (!key1) return pathVal; if (pathVal == null) return undefined; pathVal = pathVal[key1]; if (!key2) return pathVal; if (pathVal == null) return undefined; pathVal = pathVal[key2]; if (!key3) return pathVal; if (pathVal == null) return undefined; pathVal = pathVal[key3]; if (!key4) return pathVal; if (pathVal == null) return undefined; pathVal = pathVal[key4]; return pathVal; }; } function simpleGetterFn1(key0, fullExp) { ensureSafeMemberName(key0, fullExp); return function simpleGetterFn1(scope, locals) { if (scope == null) return undefined; return ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0]; }; } function simpleGetterFn2(key0, key1, fullExp) { ensureSafeMemberName(key0, fullExp); ensureSafeMemberName(key1, fullExp); return function simpleGetterFn2(scope, locals) { if (scope == null) return undefined; scope = ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0]; return scope == null ? undefined : scope[key1]; }; } function getterFn(path, options, fullExp) { // Check whether the cache has this getter already. // We can use hasOwnProperty directly on the cache because we ensure, // see below, that the cache never stores a path called 'hasOwnProperty' if (getterFnCache.hasOwnProperty(path)) { return getterFnCache[path]; } var pathKeys = path.split('.'), pathKeysLength = pathKeys.length, fn; // When we have only 1 or 2 tokens, use optimized special case closures. // http://jsperf.com/angularjs-parse-getter/6 if (pathKeysLength === 1) { fn = simpleGetterFn1(pathKeys[0], fullExp); } else if (pathKeysLength === 2) { fn = simpleGetterFn2(pathKeys[0], pathKeys[1], fullExp); } else if (options.csp) { if (pathKeysLength < 6) { fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, options); } else { fn = function(scope, locals) { var i = 0, val; do { val = cspSafeGetterFn(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], fullExp, options)(scope, locals); locals = undefined; // clear after first iteration scope = val; } while (i < pathKeysLength); return val; }; } } else { var code = 'var p;\n'; forEach(pathKeys, function(key, index) { ensureSafeMemberName(key, fullExp); code += 'if(s == null) return undefined;\n' + 's='+ (index // we simply dereference 's' on any .dot notation ? 's' // but if we are first then we check locals first, and if so read it first : '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n'; }); code += 'return s;'; /* jshint -W054 */ var evaledFnGetter = new Function('s', 'k', code); // s=scope, k=locals /* jshint +W054 */ evaledFnGetter.toString = valueFn(code); fn = evaledFnGetter; } // Only cache the value if it's not going to mess up the cache object // This is more performant that using Object.prototype.hasOwnProperty.call if (path !== 'hasOwnProperty') { getterFnCache[path] = fn; } return fn; } /////////////////////////////////// /** * @ngdoc service * @name $parse * @kind function * * @description * * Converts Angular {@link guide/expression expression} into a function. * * ```js * var getter = $parse('user.name'); * var setter = getter.assign; * var context = {user:{name:'angular'}}; * var locals = {user:{name:'local'}}; * * expect(getter(context)).toEqual('angular'); * setter(context, 'newValue'); * expect(context.user.name).toEqual('newValue'); * expect(getter(context, locals)).toEqual('local'); * ``` * * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: * * * `context` – `{object}` – an object against which any expressions embedded in the strings * are evaluated against (typically a scope object). * * `locals` – `{object=}` – local variables context object, useful for overriding values in * `context`. * * The returned function also has the following properties: * * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript * literal. * * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript * constant literals. * * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be * set to a function to change its value on the given context. * */ /** * @ngdoc provider * @name $parseProvider * @function * * @description * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} * service. */ function $ParseProvider() { var cache = {}; var $parseOptions = { csp: false }; this.$get = ['$filter', '$sniffer', '$log', function($filter, $sniffer, $log) { $parseOptions.csp = $sniffer.csp; return function(exp) { var parsedExpression, oneTime; switch (typeof exp) { case 'string': if (exp.charAt(0) === ':' && exp.charAt(1) === ':') { oneTime = true; exp = exp.substring(2); } if (cache.hasOwnProperty(exp)) { return oneTime ? oneTimeWrapper(cache[exp]) : cache[exp]; } var lexer = new Lexer($parseOptions); var parser = new Parser(lexer, $filter, $parseOptions); parsedExpression = parser.parse(exp); if (exp !== 'hasOwnProperty') { // Only cache the value if it's not going to mess up the cache object // This is more performant that using Object.prototype.hasOwnProperty.call cache[exp] = parsedExpression; } if (parsedExpression.constant) { parsedExpression.$$unwatch = true; } return oneTime ? oneTimeWrapper(parsedExpression) : parsedExpression; case 'function': return exp; default: return noop; } function oneTimeWrapper(expression) { var stable = false, lastValue; oneTimeParseFn.literal = expression.literal; oneTimeParseFn.constant = expression.constant; oneTimeParseFn.assign = expression.assign; return oneTimeParseFn; function oneTimeParseFn(self, locals) { if (!stable) { lastValue = expression(self, locals); oneTimeParseFn.$$unwatch = isDefined(lastValue); if (oneTimeParseFn.$$unwatch && self && self.$$postDigestQueue) { self.$$postDigestQueue.push(function () { // create a copy if the value is defined and it is not a $sce value if ((stable = isDefined(lastValue)) && !lastValue.$$unwrapTrustedValue) { lastValue = copy(lastValue); } }); } } return lastValue; } } }; }]; } /** * @ngdoc service * @name $q * @requires $rootScope * * @description * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q). * * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an * interface for interacting with an object that represents the result of an action that is * performed asynchronously, and may or may not be finished at any given point in time. * * From the perspective of dealing with error handling, deferred and promise APIs are to * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. * * ```js * // for the purpose of this example let's assume that variables `$q`, `scope` and `okToGreet` * // are available in the current lexical scope (they could have been injected or passed in). * * function asyncGreet(name) { * var deferred = $q.defer(); * * setTimeout(function() { * // since this fn executes async in a future turn of the event loop, we need to wrap * // our code into an $apply call so that the model changes are properly observed. * scope.$apply(function() { * deferred.notify('About to greet ' + name + '.'); * * if (okToGreet(name)) { * deferred.resolve('Hello, ' + name + '!'); * } else { * deferred.reject('Greeting ' + name + ' is not allowed.'); * } * }); * }, 1000); * * return deferred.promise; * } * * var promise = asyncGreet('Robin Hood'); * promise.then(function(greeting) { * alert('Success: ' + greeting); * }, function(reason) { * alert('Failed: ' + reason); * }, function(update) { * alert('Got notification: ' + update); * }); * ``` * * At first it might not be obvious why this extra complexity is worth the trouble. The payoff * comes in the way of guarantees that promise and deferred APIs make, see * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. * * Additionally the promise api allows for composition that is very hard to do with the * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the * section on serial or parallel joining of promises. * * * # The Deferred API * * A new instance of deferred is constructed by calling `$q.defer()`. * * The purpose of the deferred object is to expose the associated Promise instance as well as APIs * that can be used for signaling the successful or unsuccessful completion, as well as the status * of the task. * * **Methods** * * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection * constructed via `$q.reject`, the promise will be rejected instead. * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to * resolving it with a rejection constructed via `$q.reject`. * - `notify(value)` - provides updates on the status of the promise's execution. This may be called * multiple times before the promise is either resolved or rejected. * * **Properties** * * - promise – `{Promise}` – promise object associated with this deferred. * * * # The Promise API * * A new promise instance is created when a deferred instance is created and can be retrieved by * calling `deferred.promise`. * * The purpose of the promise object is to allow for interested parties to get access to the result * of the deferred task when it completes. * * **Methods** * * - `then(successCallback, errorCallback, notifyCallback)` – regardless of when the promise was or * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously * as soon as the result is available. The callbacks are called with a single argument: the result * or rejection reason. Additionally, the notify callback may be called zero or more times to * provide a progress indication, before the promise is resolved or rejected. * * This method *returns a new promise* which is resolved or rejected via the return value of the * `successCallback`, `errorCallback`. It also notifies via the return value of the * `notifyCallback` method. The promise can not be resolved or rejected from the notifyCallback * method. * * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)` * * - `finally(callback)` – allows you to observe either the fulfillment or rejection of a promise, * but to do so without modifying the final value. This is useful to release resources or do some * clean-up that needs to be done whether the promise was rejected or resolved. See the [full * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for * more information. * * Because `finally` is a reserved word in JavaScript and reserved keywords are not supported as * property names by ES3, you'll need to invoke the method like `promise['finally'](callback)` to * make your code IE8 and Android 2.x compatible. * * # Chaining promises * * Because calling the `then` method of a promise returns a new derived promise, it is easily * possible to create a chain of promises: * * ```js * promiseB = promiseA.then(function(result) { * return result + 1; * }); * * // promiseB will be resolved immediately after promiseA is resolved and its value * // will be the result of promiseA incremented by 1 * ``` * * It is possible to create chains of any length and since a promise can be resolved with another * promise (which will defer its resolution further), it is possible to pause/defer resolution of * the promises at any point in the chain. This makes it possible to implement powerful APIs like * $http's response interceptors. * * * # Differences between Kris Kowal's Q and $q * * There are two main differences: * * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation * mechanism in angular, which means faster propagation of resolution or rejection into your * models and avoiding unnecessary browser repaints, which would result in flickering UI. * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains * all the important functionality needed for common async tasks. * * # Testing * * ```js * it('should simulate promise', inject(function($q, $rootScope) { * var deferred = $q.defer(); * var promise = deferred.promise; * var resolvedValue; * * promise.then(function(value) { resolvedValue = value; }); * expect(resolvedValue).toBeUndefined(); * * // Simulate resolving of promise * deferred.resolve(123); * // Note that the 'then' function does not get called synchronously. * // This is because we want the promise API to always be async, whether or not * // it got called synchronously or asynchronously. * expect(resolvedValue).toBeUndefined(); * * // Propagate promise resolution to 'then' functions using $apply(). * $rootScope.$apply(); * expect(resolvedValue).toEqual(123); * })); * ``` */ function $QProvider() { this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { return qFactory(function(callback) { $rootScope.$evalAsync(callback); }, $exceptionHandler); }]; } /** * Constructs a promise manager. * * @param {function(Function)} nextTick Function for executing functions in the next turn. * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for * debugging purposes. * @returns {object} Promise manager. */ function qFactory(nextTick, exceptionHandler) { /** * @ngdoc method * @name $q#defer * @function * * @description * Creates a `Deferred` object which represents a task which will finish in the future. * * @returns {Deferred} Returns a new instance of deferred. */ var defer = function() { var pending = [], value, deferred; deferred = { resolve: function(val) { if (pending) { var callbacks = pending; pending = undefined; value = ref(val); if (callbacks.length) { nextTick(function() { var callback; for (var i = 0, ii = callbacks.length; i < ii; i++) { callback = callbacks[i]; value.then(callback[0], callback[1], callback[2]); } }); } } }, reject: function(reason) { deferred.resolve(createInternalRejectedPromise(reason)); }, notify: function(progress) { if (pending) { var callbacks = pending; if (pending.length) { nextTick(function() { var callback; for (var i = 0, ii = callbacks.length; i < ii; i++) { callback = callbacks[i]; callback[2](progress); } }); } } }, promise: { then: function(callback, errback, progressback) { var result = defer(); var wrappedCallback = function(value) { try { result.resolve((isFunction(callback) ? callback : defaultCallback)(value)); } catch(e) { result.reject(e); exceptionHandler(e); } }; var wrappedErrback = function(reason) { try { result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); } catch(e) { result.reject(e); exceptionHandler(e); } }; var wrappedProgressback = function(progress) { try { result.notify((isFunction(progressback) ? progressback : defaultCallback)(progress)); } catch(e) { exceptionHandler(e); } }; if (pending) { pending.push([wrappedCallback, wrappedErrback, wrappedProgressback]); } else { value.then(wrappedCallback, wrappedErrback, wrappedProgressback); } return result.promise; }, "catch": function(callback) { return this.then(null, callback); }, "finally": function(callback) { function makePromise(value, resolved) { var result = defer(); if (resolved) { result.resolve(value); } else { result.reject(value); } return result.promise; } function handleCallback(value, isResolved) { var callbackOutput = null; try { callbackOutput = (callback ||defaultCallback)(); } catch(e) { return makePromise(e, false); } if (callbackOutput && isFunction(callbackOutput.then)) { return callbackOutput.then(function() { return makePromise(value, isResolved); }, function(error) { return makePromise(error, false); }); } else { return makePromise(value, isResolved); } } return this.then(function(value) { return handleCallback(value, true); }, function(error) { return handleCallback(error, false); }); } } }; return deferred; }; var ref = function(value) { if (value && isFunction(value.then)) return value; return { then: function(callback) { var result = defer(); nextTick(function() { result.resolve(callback(value)); }); return result.promise; } }; }; /** * @ngdoc method * @name $q#reject * @function * * @description * Creates a promise that is resolved as rejected with the specified `reason`. This api should be * used to forward rejection in a chain of promises. If you are dealing with the last promise in * a promise chain, you don't need to worry about it. * * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via * a promise error callback and you want to forward the error to the promise derived from the * current promise, you have to "rethrow" the error by returning a rejection constructed via * `reject`. * * ```js * promiseB = promiseA.then(function(result) { * // success: do something and resolve promiseB * // with the old or a new result * return result; * }, function(reason) { * // error: handle the error if possible and * // resolve promiseB with newPromiseOrValue, * // otherwise forward the rejection to promiseB * if (canHandle(reason)) { * // handle the error and recover * return newPromiseOrValue; * } * return $q.reject(reason); * }); * ``` * * @param {*} reason Constant, message, exception or an object representing the rejection reason. * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. */ var reject = function(reason) { var result = defer(); result.reject(reason); return result.promise; }; var createInternalRejectedPromise = function(reason) { return { then: function(callback, errback) { var result = defer(); nextTick(function() { try { result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); } catch(e) { result.reject(e); exceptionHandler(e); } }); return result.promise; } }; }; /** * @ngdoc method * @name $q#when * @function * * @description * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. * This is useful when you are dealing with an object that might or might not be a promise, or if * the promise comes from a source that can't be trusted. * * @param {*} value Value or a promise * @returns {Promise} Returns a promise of the passed value or promise */ var when = function(value, callback, errback, progressback) { var result = defer(), done; var wrappedCallback = function(value) { try { return (isFunction(callback) ? callback : defaultCallback)(value); } catch (e) { exceptionHandler(e); return reject(e); } }; var wrappedErrback = function(reason) { try { return (isFunction(errback) ? errback : defaultErrback)(reason); } catch (e) { exceptionHandler(e); return reject(e); } }; var wrappedProgressback = function(progress) { try { return (isFunction(progressback) ? progressback : defaultCallback)(progress); } catch (e) { exceptionHandler(e); } }; nextTick(function() { ref(value).then(function(value) { if (done) return; done = true; result.resolve(ref(value).then(wrappedCallback, wrappedErrback, wrappedProgressback)); }, function(reason) { if (done) return; done = true; result.resolve(wrappedErrback(reason)); }, function(progress) { if (done) return; result.notify(wrappedProgressback(progress)); }); }); return result.promise; }; function defaultCallback(value) { return value; } function defaultErrback(reason) { return reject(reason); } /** * @ngdoc method * @name $q#all * @function * * @description * Combines multiple promises into a single promise that is resolved when all of the input * promises are resolved. * * @param {Array.|Object.} promises An array or hash of promises. * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, * each value corresponding to the promise at the same index/key in the `promises` array/hash. * If any of the promises is resolved with a rejection, this resulting promise will be rejected * with the same rejection value. */ function all(promises) { var deferred = defer(), counter = 0, results = isArray(promises) ? [] : {}; forEach(promises, function(promise, key) { counter++; ref(promise).then(function(value) { if (results.hasOwnProperty(key)) return; results[key] = value; if (!(--counter)) deferred.resolve(results); }, function(reason) { if (results.hasOwnProperty(key)) return; deferred.reject(reason); }); }); if (counter === 0) { deferred.resolve(results); } return deferred.promise; } return { defer: defer, reject: reject, when: when, all: all }; } function $$RAFProvider(){ //rAF this.$get = ['$window', '$timeout', function($window, $timeout) { var requestAnimationFrame = $window.requestAnimationFrame || $window.webkitRequestAnimationFrame || $window.mozRequestAnimationFrame; var cancelAnimationFrame = $window.cancelAnimationFrame || $window.webkitCancelAnimationFrame || $window.mozCancelAnimationFrame || $window.webkitCancelRequestAnimationFrame; var rafSupported = !!requestAnimationFrame; var raf = rafSupported ? function(fn) { var id = requestAnimationFrame(fn); return function() { cancelAnimationFrame(id); }; } : function(fn) { var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 return function() { $timeout.cancel(timer); }; }; raf.supported = rafSupported; return raf; }]; } /** * DESIGN NOTES * * The design decisions behind the scope are heavily favored for speed and memory consumption. * * The typical use of scope is to watch the expressions, which most of the time return the same * value as last time so we optimize the operation. * * Closures construction is expensive in terms of speed as well as memory: * - No closures, instead use prototypical inheritance for API * - Internal state needs to be stored on scope directly, which means that private state is * exposed as $$____ properties * * Loop operations are optimized by using while(count--) { ... } * - this means that in order to keep the same order of execution as addition we have to add * items to the array at the beginning (unshift) instead of at the end (push) * * Child scopes are created and removed often * - Using an array would be slow since inserts in middle are expensive so we use linked list * * There are few watches then a lot of observers. This is why you don't want the observer to be * implemented in the same way as watch. Watch requires return of initialization function which * are expensive to construct. */ /** * @ngdoc provider * @name $rootScopeProvider * @description * * Provider for the $rootScope service. */ /** * @ngdoc method * @name $rootScopeProvider#digestTtl * @description * * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and * assuming that the model is unstable. * * The current default is 10 iterations. * * In complex applications it's possible that the dependencies between `$watch`s will result in * several digest iterations. However if an application needs more than the default 10 digest * iterations for its model to stabilize then you should investigate what is causing the model to * continuously change during the digest. * * Increasing the TTL could have performance implications, so you should not change it without * proper justification. * * @param {number} limit The number of digest iterations. */ /** * @ngdoc service * @name $rootScope * @description * * Every application has a single root {@link ng.$rootScope.Scope scope}. * All other scopes are descendant scopes of the root scope. Scopes provide separation * between the model and the view, via a mechanism for watching the model for changes. * They also provide an event emission/broadcast and subscription facility. See the * {@link guide/scope developer guide on scopes}. */ function $RootScopeProvider(){ var TTL = 10; var $rootScopeMinErr = minErr('$rootScope'); var lastDirtyWatch = null; this.digestTtl = function(value) { if (arguments.length) { TTL = value; } return TTL; }; this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', function( $injector, $exceptionHandler, $parse, $browser) { /** * @ngdoc type * @name $rootScope.Scope * * @description * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the * {@link auto.$injector $injector}. Child scopes are created using the * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when * compiled HTML template is executed.) * * Here is a simple scope snippet to show how you can interact with the scope. * ```html * * ``` * * # Inheritance * A scope can inherit from a parent scope, as in this example: * ```js var parent = $rootScope; var child = parent.$new(); parent.salutation = "Hello"; child.name = "World"; expect(child.salutation).toEqual('Hello'); child.salutation = "Welcome"; expect(child.salutation).toEqual('Welcome'); expect(parent.salutation).toEqual('Hello'); * ``` * * * @param {Object.=} providers Map of service factory which need to be * provided for the current scope. Defaults to {@link ng}. * @param {Object.=} instanceCache Provides pre-instantiated services which should * append/override services provided by `providers`. This is handy * when unit-testing and having the need to override a default * service. * @returns {Object} Newly created scope. * */ function Scope() { this.$id = nextUid(); this.$$phase = this.$parent = this.$$watchers = this.$$nextSibling = this.$$prevSibling = this.$$childHead = this.$$childTail = null; this['this'] = this.$root = this; this.$$destroyed = false; this.$$asyncQueue = []; this.$$postDigestQueue = []; this.$$listeners = {}; this.$$listenerCount = {}; this.$$isolateBindings = {}; } /** * @ngdoc property * @name $rootScope.Scope#$id * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for * debugging. */ Scope.prototype = { constructor: Scope, /** * @ngdoc method * @name $rootScope.Scope#$new * @function * * @description * Creates a new child {@link ng.$rootScope.Scope scope}. * * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} and * {@link ng.$rootScope.Scope#$digest $digest()} events. The scope can be removed from the * scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. * * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is * desired for the scope and its child scopes to be permanently detached from the parent and * thus stop participating in model change detection and listener notification by invoking. * * @param {boolean} isolate If true, then the scope does not prototypically inherit from the * parent scope. The scope is isolated, as it can not see parent scope properties. * When creating widgets, it is useful for the widget to not accidentally read parent * state. * * @returns {Object} The newly created child scope. * */ $new: function(isolate) { var ChildScope, child; if (isolate) { child = new Scope(); child.$root = this.$root; // ensure that there is just one async queue per $rootScope and its children child.$$asyncQueue = this.$$asyncQueue; child.$$postDigestQueue = this.$$postDigestQueue; } else { // Only create a child scope class if somebody asks for one, // but cache it to allow the VM to optimize lookups. if (!this.$$childScopeClass) { this.$$childScopeClass = function() { this.$$watchers = this.$$nextSibling = this.$$childHead = this.$$childTail = null; this.$$listeners = {}; this.$$listenerCount = {}; this.$id = nextUid(); this.$$childScopeClass = null; }; this.$$childScopeClass.prototype = this; } child = new this.$$childScopeClass(); } child['this'] = child; child.$parent = this; child.$$prevSibling = this.$$childTail; if (this.$$childHead) { this.$$childTail.$$nextSibling = child; this.$$childTail = child; } else { this.$$childHead = this.$$childTail = child; } return child; }, /** * @ngdoc method * @name $rootScope.Scope#$watch * @function * * @description * Registers a `listener` callback to be executed whenever the `watchExpression` changes. * * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest * $digest()} and should return the value that will be watched. (Since * {@link ng.$rootScope.Scope#$digest $digest()} reruns when it detects changes the * `watchExpression` can execute multiple times per * {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.) * - The `listener` is called only when the value from the current `watchExpression` and the * previous call to `watchExpression` are not equal (with the exception of the initial run, * see below). The inequality is determined according to * {@link angular.equals} function. To save the value of the object for later comparison, * the {@link angular.copy} function is used. It also means that watching complex options * will have adverse memory and performance implications. * - The watch `listener` may change the model, which may trigger other `listener`s to fire. * This is achieved by rerunning the watchers until no changes are detected. The rerun * iteration limit is 10 to prevent an infinite loop deadlock. * * * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, * you can register a `watchExpression` function with no `listener`. (Since `watchExpression` * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a * change is detected, be prepared for multiple calls to your listener.) * * After a watcher is registered with the scope, the `listener` fn is called asynchronously * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the * watcher. In rare cases, this is undesirable because the listener is called when the result * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the * listener was called due to initialization. * * The example below contains an illustration of using a function as your $watch listener * * * # Example * ```js // let's assume that scope was dependency injected as the $rootScope var scope = $rootScope; scope.name = 'misko'; scope.counter = 0; expect(scope.counter).toEqual(0); scope.$watch('name', function(newValue, oldValue) { scope.counter = scope.counter + 1; }); expect(scope.counter).toEqual(0); scope.$digest(); // no variable change expect(scope.counter).toEqual(0); scope.name = 'adam'; scope.$digest(); expect(scope.counter).toEqual(1); // Using a listener function var food; scope.foodCounter = 0; expect(scope.foodCounter).toEqual(0); scope.$watch( // This is the listener function function() { return food; }, // This is the change handler function(newValue, oldValue) { if ( newValue !== oldValue ) { // Only increment the counter if the value changed scope.foodCounter = scope.foodCounter + 1; } } ); // No digest has been run so the counter will be zero expect(scope.foodCounter).toEqual(0); // Run the digest but since food has not changed count will still be zero scope.$digest(); expect(scope.foodCounter).toEqual(0); // Update food and run digest. Now the counter will increment food = 'cheeseburger'; scope.$digest(); expect(scope.foodCounter).toEqual(1); * ``` * * * * @param {(function()|string)} watchExpression Expression that is evaluated on each * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers * a call to the `listener`. * * - `string`: Evaluated as {@link guide/expression expression} * - `function(scope)`: called with current `scope` as a parameter. * @param {(function()|string)=} listener Callback called whenever the return value of * the `watchExpression` changes. * * - `string`: Evaluated as {@link guide/expression expression} * - `function(newValue, oldValue, scope)`: called with current and previous values as * parameters. * * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of * comparing for reference equality. * @returns {function()} Returns a deregistration function for this listener. */ $watch: function(watchExp, listener, objectEquality) { var scope = this, get = compileToFn(watchExp, 'watch'), array = scope.$$watchers, watcher = { fn: listener, last: initWatchVal, get: get, exp: watchExp, eq: !!objectEquality }; lastDirtyWatch = null; // in the case user pass string, we need to compile it, do we really need this ? if (!isFunction(listener)) { var listenFn = compileToFn(listener || noop, 'listener'); watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);}; } if (!array) { array = scope.$$watchers = []; } // we use unshift since we use a while loop in $digest for speed. // the while loop reads in reverse order. array.unshift(watcher); return function deregisterWatch() { arrayRemove(array, watcher); lastDirtyWatch = null; }; }, /** * @ngdoc method * @name $rootScope.Scope#$watchGroup * @function * * @description * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`. * If any one expression in the collection changes the `listener` is executed. * * - The items in the `watchCollection` array are observed via standard $watch operation and are examined on every * call to $digest() to see if any items changes. * - The `listener` is called whenever any expression in the `watchExpressions` array changes. * * @param {Array.} watchExpressions Array of expressions that will be individually * watched using {@link ng.$rootScope.Scope#$watch $watch()} * * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any * expression in `watchExpressions` changes * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching * those of `watchExpression` * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching * those of `watchExpression` * The `scope` refers to the current scope. * * @returns {function()} Returns a de-registration function for all listeners. */ $watchGroup: function(watchExpressions, listener) { var oldValues = new Array(watchExpressions.length); var newValues = new Array(watchExpressions.length); var deregisterFns = []; var changeCount = 0; var self = this; var unwatchFlags = new Array(watchExpressions.length); var unwatchCount = watchExpressions.length; forEach(watchExpressions, function (expr, i) { var exprFn = $parse(expr); deregisterFns.push(self.$watch(exprFn, function (value, oldValue) { newValues[i] = value; oldValues[i] = oldValue; changeCount++; if (unwatchFlags[i] && !exprFn.$$unwatch) unwatchCount++; if (!unwatchFlags[i] && exprFn.$$unwatch) unwatchCount--; unwatchFlags[i] = exprFn.$$unwatch; })); }, this); deregisterFns.push(self.$watch(watchGroupFn, function () { listener(newValues, oldValues, self); if (unwatchCount === 0) { watchGroupFn.$$unwatch = true; } else { watchGroupFn.$$unwatch = false; } })); return function deregisterWatchGroup() { forEach(deregisterFns, function (fn) { fn(); }); }; function watchGroupFn() {return changeCount;} }, /** * @ngdoc method * @name $rootScope.Scope#$watchCollection * @function * * @description * Shallow watches the properties of an object and fires whenever any of the properties change * (for arrays, this implies watching the array items; for object maps, this implies watching * the properties). If a change is detected, the `listener` callback is fired. * * - The `obj` collection is observed via standard $watch operation and is examined on every * call to $digest() to see if any items have been added, removed, or moved. * - The `listener` is called whenever anything within the `obj` has changed. Examples include * adding, removing, and moving items belonging to an object or array. * * * # Example * ```js $scope.names = ['igor', 'matias', 'misko', 'james']; $scope.dataCount = 4; $scope.$watchCollection('names', function(newNames, oldNames) { $scope.dataCount = newNames.length; }); expect($scope.dataCount).toEqual(4); $scope.$digest(); //still at 4 ... no changes expect($scope.dataCount).toEqual(4); $scope.names.pop(); $scope.$digest(); //now there's been a change expect($scope.dataCount).toEqual(3); * ``` * * * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The * expression value should evaluate to an object or an array which is observed on each * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the * collection will trigger a call to the `listener`. * * @param {function(newCollection, oldCollection, scope)} listener a callback function called * when a change is detected. * - The `newCollection` object is the newly modified data obtained from the `obj` expression * - The `oldCollection` object is a copy of the former collection data. * Due to performance considerations, the`oldCollection` value is computed only if the * `listener` function declares two or more arguments. * - The `scope` argument refers to the current scope. * * @returns {function()} Returns a de-registration function for this listener. When the * de-registration function is executed, the internal watch operation is terminated. */ $watchCollection: function(obj, listener) { var self = this; // the current value, updated on each dirty-check run var newValue; // a shallow copy of the newValue from the last dirty-check run, // updated to match newValue during dirty-check run var oldValue; // a shallow copy of the newValue from when the last change happened var veryOldValue; // only track veryOldValue if the listener is asking for it var trackVeryOldValue = (listener.length > 1); var changeDetected = 0; var objGetter = $parse(obj); var internalArray = []; var internalObject = {}; var initRun = true; var oldLength = 0; function $watchCollectionWatch() { newValue = objGetter(self); var newLength, key; if (!isObject(newValue)) { // if primitive if (oldValue !== newValue) { oldValue = newValue; changeDetected++; } } else if (isArrayLike(newValue)) { if (oldValue !== internalArray) { // we are transitioning from something which was not an array into array. oldValue = internalArray; oldLength = oldValue.length = 0; changeDetected++; } newLength = newValue.length; if (oldLength !== newLength) { // if lengths do not match we need to trigger change notification changeDetected++; oldValue.length = oldLength = newLength; } // copy the items to oldValue and look for changes. for (var i = 0; i < newLength; i++) { var bothNaN = (oldValue[i] !== oldValue[i]) && (newValue[i] !== newValue[i]); if (!bothNaN && (oldValue[i] !== newValue[i])) { changeDetected++; oldValue[i] = newValue[i]; } } } else { if (oldValue !== internalObject) { // we are transitioning from something which was not an object into object. oldValue = internalObject = {}; oldLength = 0; changeDetected++; } // copy the items to oldValue and look for changes. newLength = 0; for (key in newValue) { if (newValue.hasOwnProperty(key)) { newLength++; if (oldValue.hasOwnProperty(key)) { if (oldValue[key] !== newValue[key]) { changeDetected++; oldValue[key] = newValue[key]; } } else { oldLength++; oldValue[key] = newValue[key]; changeDetected++; } } } if (oldLength > newLength) { // we used to have more keys, need to find them and destroy them. changeDetected++; for(key in oldValue) { if (oldValue.hasOwnProperty(key) && !newValue.hasOwnProperty(key)) { oldLength--; delete oldValue[key]; } } } } $watchCollectionWatch.$$unwatch = objGetter.$$unwatch; return changeDetected; } function $watchCollectionAction() { if (initRun) { initRun = false; listener(newValue, newValue, self); } else { listener(newValue, veryOldValue, self); } // make a copy for the next time a collection is changed if (trackVeryOldValue) { if (!isObject(newValue)) { //primitive veryOldValue = newValue; } else if (isArrayLike(newValue)) { veryOldValue = new Array(newValue.length); for (var i = 0; i < newValue.length; i++) { veryOldValue[i] = newValue[i]; } } else { // if object veryOldValue = {}; for (var key in newValue) { if (hasOwnProperty.call(newValue, key)) { veryOldValue[key] = newValue[key]; } } } } } return this.$watch($watchCollectionWatch, $watchCollectionAction); }, /** * @ngdoc method * @name $rootScope.Scope#$digest * @function * * @description * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} * until no more listeners are firing. This means that it is possible to get into an infinite * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of * iterations exceeds 10. * * Usually, you don't call `$digest()` directly in * {@link ng.directive:ngController controllers} or in * {@link ng.$compileProvider#directive directives}. * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`. * * If you want to be notified whenever `$digest()` is called, * you can register a `watchExpression` function with * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. * * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. * * # Example * ```js var scope = ...; scope.name = 'misko'; scope.counter = 0; expect(scope.counter).toEqual(0); scope.$watch('name', function(newValue, oldValue) { scope.counter = scope.counter + 1; }); expect(scope.counter).toEqual(0); scope.$digest(); // no variable change expect(scope.counter).toEqual(0); scope.name = 'adam'; scope.$digest(); expect(scope.counter).toEqual(1); * ``` * */ $digest: function() { var watch, value, last, watchers, asyncQueue = this.$$asyncQueue, postDigestQueue = this.$$postDigestQueue, length, dirty, ttl = TTL, next, current, target = this, watchLog = [], stableWatchesCandidates = [], logIdx, logMsg, asyncTask; beginPhase('$digest'); lastDirtyWatch = null; do { // "while dirty" loop dirty = false; current = target; while(asyncQueue.length) { try { asyncTask = asyncQueue.shift(); asyncTask.scope.$eval(asyncTask.expression); } catch (e) { clearPhase(); $exceptionHandler(e); } lastDirtyWatch = null; } traverseScopesLoop: do { // "traverse the scopes" loop if ((watchers = current.$$watchers)) { // process our watches length = watchers.length; while (length--) { try { watch = watchers[length]; // Most common watches are on primitives, in which case we can short // circuit it with === operator, only when === fails do we use .equals if (watch) { if ((value = watch.get(current)) !== (last = watch.last) && !(watch.eq ? equals(value, last) : (typeof value == 'number' && typeof last == 'number' && isNaN(value) && isNaN(last)))) { dirty = true; lastDirtyWatch = watch; watch.last = watch.eq ? copy(value) : value; watch.fn(value, ((last === initWatchVal) ? value : last), current); if (ttl < 5) { logIdx = 4 - ttl; if (!watchLog[logIdx]) watchLog[logIdx] = []; logMsg = (isFunction(watch.exp)) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp; logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last); watchLog[logIdx].push(logMsg); } if (watch.get.$$unwatch) stableWatchesCandidates.push({watch: watch, array: watchers}); } else if (watch === lastDirtyWatch) { // If the most recently dirty watcher is now clean, short circuit since the remaining watchers // have already been tested. dirty = false; break traverseScopesLoop; } } } catch (e) { clearPhase(); $exceptionHandler(e); } } } // Insanity Warning: scope depth-first traversal // yes, this code is a bit crazy, but it works and we have tests to prove it! // this piece should be kept in sync with the traversal in $broadcast if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) { while(current !== target && !(next = current.$$nextSibling)) { current = current.$parent; } } } while ((current = next)); // `break traverseScopesLoop;` takes us to here if((dirty || asyncQueue.length) && !(ttl--)) { clearPhase(); throw $rootScopeMinErr('infdig', '{0} $digest() iterations reached. Aborting!\n' + 'Watchers fired in the last 5 iterations: {1}', TTL, toJson(watchLog)); } } while (dirty || asyncQueue.length); clearPhase(); while(postDigestQueue.length) { try { postDigestQueue.shift()(); } catch (e) { $exceptionHandler(e); } } for (length = stableWatchesCandidates.length - 1; length >= 0; --length) { var candidate = stableWatchesCandidates[length]; if (candidate.watch.get.$$unwatch) { arrayRemove(candidate.array, candidate.watch); } } }, /** * @ngdoc event * @name $rootScope.Scope#$destroy * @eventType broadcast on scope being destroyed * * @description * Broadcasted when a scope and its children are being destroyed. * * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to * clean up DOM bindings before an element is removed from the DOM. */ /** * @ngdoc method * @name $rootScope.Scope#$destroy * @function * * @description * Removes the current scope (and all of its children) from the parent scope. Removal implies * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer * propagate to the current scope and its children. Removal also implies that the current * scope is eligible for garbage collection. * * The `$destroy()` is usually used by directives such as * {@link ng.directive:ngRepeat ngRepeat} for managing the * unrolling of the loop. * * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. * Application code can register a `$destroy` event handler that will give it a chance to * perform any necessary cleanup. * * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to * clean up DOM bindings before an element is removed from the DOM. */ $destroy: function() { // we can't destroy the root scope or a scope that has been already destroyed if (this.$$destroyed) return; var parent = this.$parent; this.$broadcast('$destroy'); this.$$destroyed = true; if (this === $rootScope) return; forEach(this.$$listenerCount, bind(null, decrementListenerCount, this)); // sever all the references to parent scopes (after this cleanup, the current scope should // not be retained by any of our references and should be eligible for garbage collection) if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; // All of the code below is bogus code that works around V8's memory leak via optimized code // and inline caches. // // see: // - https://code.google.com/p/v8/issues/detail?id=2073#c26 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead = this.$$childTail = this.$root = null; // don't reset these to null in case some async task tries to register a listener/watch/task this.$$listeners = {}; this.$$watchers = this.$$asyncQueue = this.$$postDigestQueue = []; // prevent NPEs since these methods have references to properties we nulled out this.$destroy = this.$digest = this.$apply = noop; this.$on = this.$watch = this.$watchGroup = function() { return noop; }; }, /** * @ngdoc method * @name $rootScope.Scope#$eval * @function * * @description * Executes the `expression` on the current scope and returns the result. Any exceptions in * the expression are propagated (uncaught). This is useful when evaluating Angular * expressions. * * # Example * ```js var scope = ng.$rootScope.Scope(); scope.a = 1; scope.b = 2; expect(scope.$eval('a+b')).toEqual(3); expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); * ``` * * @param {(string|function())=} expression An angular expression to be executed. * * - `string`: execute using the rules as defined in {@link guide/expression expression}. * - `function(scope)`: execute the function with the current `scope` parameter. * * @param {(object)=} locals Local variables object, useful for overriding values in scope. * @returns {*} The result of evaluating the expression. */ $eval: function(expr, locals) { return $parse(expr)(this, locals); }, /** * @ngdoc method * @name $rootScope.Scope#$evalAsync * @function * * @description * Executes the expression on the current scope at a later point in time. * * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only * that: * * - it will execute after the function that scheduled the evaluation (preferably before DOM * rendering). * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after * `expression` execution. * * Any exceptions from the execution of the expression are forwarded to the * {@link ng.$exceptionHandler $exceptionHandler} service. * * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle * will be scheduled. However, it is encouraged to always call code that changes the model * from within an `$apply` call. That includes code evaluated via `$evalAsync`. * * @param {(string|function())=} expression An angular expression to be executed. * * - `string`: execute using the rules as defined in {@link guide/expression expression}. * - `function(scope)`: execute the function with the current `scope` parameter. * */ $evalAsync: function(expr) { // if we are outside of an $digest loop and this is the first time we are scheduling async // task also schedule async auto-flush if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) { $browser.defer(function() { if ($rootScope.$$asyncQueue.length) { $rootScope.$digest(); } }); } this.$$asyncQueue.push({scope: this, expression: expr}); }, $$postDigest : function(fn) { this.$$postDigestQueue.push(fn); }, /** * @ngdoc method * @name $rootScope.Scope#$apply * @function * * @description * `$apply()` is used to execute an expression in angular from outside of the angular * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). * Because we are calling into the angular framework we need to perform proper scope life * cycle of {@link ng.$exceptionHandler exception handling}, * {@link ng.$rootScope.Scope#$digest executing watches}. * * ## Life cycle * * # Pseudo-Code of `$apply()` * ```js function $apply(expr) { try { return $eval(expr); } catch (e) { $exceptionHandler(e); } finally { $root.$digest(); } } * ``` * * * Scope's `$apply()` method transitions through the following stages: * * 1. The {@link guide/expression expression} is executed using the * {@link ng.$rootScope.Scope#$eval $eval()} method. * 2. Any exceptions from the execution of the expression are forwarded to the * {@link ng.$exceptionHandler $exceptionHandler} service. * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. * * * @param {(string|function())=} exp An angular expression to be executed. * * - `string`: execute using the rules as defined in {@link guide/expression expression}. * - `function(scope)`: execute the function with current `scope` parameter. * * @returns {*} The result of evaluating the expression. */ $apply: function(expr) { try { beginPhase('$apply'); return this.$eval(expr); } catch (e) { $exceptionHandler(e); } finally { clearPhase(); try { $rootScope.$digest(); } catch (e) { $exceptionHandler(e); throw e; } } }, /** * @ngdoc method * @name $rootScope.Scope#$on * @function * * @description * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for * discussion of event life cycle. * * The event listener function format is: `function(event, args...)`. The `event` object * passed into the listener has the following attributes: * * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or * `$broadcast`-ed. * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the * event propagates through the scope hierarchy, this property is set to null. * - `name` - `{string}`: name of the event. * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel * further event propagation (available only for events that were `$emit`-ed). * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag * to true. * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. * * @param {string} name Event name to listen on. * @param {function(event, ...args)} listener Function to call when the event is emitted. * @returns {function()} Returns a deregistration function for this listener. */ $on: function(name, listener) { var namedListeners = this.$$listeners[name]; if (!namedListeners) { this.$$listeners[name] = namedListeners = []; } namedListeners.push(listener); var current = this; do { if (!current.$$listenerCount[name]) { current.$$listenerCount[name] = 0; } current.$$listenerCount[name]++; } while ((current = current.$parent)); var self = this; return function() { namedListeners[indexOf(namedListeners, listener)] = null; decrementListenerCount(self, 1, name); }; }, /** * @ngdoc method * @name $rootScope.Scope#$emit * @function * * @description * Dispatches an event `name` upwards through the scope hierarchy notifying the * registered {@link ng.$rootScope.Scope#$on} listeners. * * The event life cycle starts at the scope on which `$emit` was called. All * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get * notified. Afterwards, the event traverses upwards toward the root scope and calls all * registered listeners along the way. The event will stop propagating if one of the listeners * cancels it. * * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed * onto the {@link ng.$exceptionHandler $exceptionHandler} service. * * @param {string} name Event name to emit. * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). */ $emit: function(name, args) { var empty = [], namedListeners, scope = this, stopPropagation = false, event = { name: name, targetScope: scope, stopPropagation: function() {stopPropagation = true;}, preventDefault: function() { event.defaultPrevented = true; }, defaultPrevented: false }, listenerArgs = concat([event], arguments, 1), i, length; do { namedListeners = scope.$$listeners[name] || empty; event.currentScope = scope; for (i=0, length=namedListeners.length; i= 8 ) { normalizedVal = urlResolve(uri).href; if (normalizedVal !== '' && !normalizedVal.match(regex)) { return 'unsafe:'+normalizedVal; } } return uri; }; }; } var $sceMinErr = minErr('$sce'); var SCE_CONTEXTS = { HTML: 'html', CSS: 'css', URL: 'url', // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a // url. (e.g. ng-include, script src, templateUrl) RESOURCE_URL: 'resourceUrl', JS: 'js' }; // Helper functions follow. // Copied from: // http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962 // Prereq: s is a string. function escapeForRegexp(s) { return s.replace(/([-()\[\]{}+?*.$\^|,:# -1) { throw $sceMinErr('iwcard', 'Illegal sequence *** in string matcher. String: {0}', matcher); } matcher = escapeForRegexp(matcher). replace('\\*\\*', '.*'). replace('\\*', '[^:/.?&;]*'); return new RegExp('^' + matcher + '$'); } else if (isRegExp(matcher)) { // The only other type of matcher allowed is a Regexp. // Match entire URL / disallow partial matches. // Flags are reset (i.e. no global, ignoreCase or multiline) return new RegExp('^' + matcher.source + '$'); } else { throw $sceMinErr('imatcher', 'Matchers may only be "self", string patterns or RegExp objects'); } } function adjustMatchers(matchers) { var adjustedMatchers = []; if (isDefined(matchers)) { forEach(matchers, function(matcher) { adjustedMatchers.push(adjustMatcher(matcher)); }); } return adjustedMatchers; } /** * @ngdoc service * @name $sceDelegate * @function * * @description * * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict * Contextual Escaping (SCE)} services to AngularJS. * * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things * work because `$sce` delegates to `$sceDelegate` for these operations. * * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. * * The default instance of `$sceDelegate` should work out of the box with little pain. While you * can override it completely to change the behavior of `$sce`, the common case would * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist * $sceDelegateProvider.resourceUrlWhitelist} and {@link * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} */ /** * @ngdoc provider * @name $sceDelegateProvider * @description * * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure * that the URLs used for sourcing Angular templates are safe. Refer {@link * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} * * For the general details about this service in Angular, read the main page for {@link ng.$sce * Strict Contextual Escaping (SCE)}. * * **Example**: Consider the following case. * * - your app is hosted at url `http://myapp.example.com/` * - but some of your templates are hosted on other domains you control such as * `http://srv01.assets.example.com/`,  `http://srv02.assets.example.com/`, etc. * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. * * Here is what a secure configuration for this scenario might look like: * *
       *    angular.module('myApp', []).config(function($sceDelegateProvider) {
       *      $sceDelegateProvider.resourceUrlWhitelist([
       *        // Allow same origin resource loads.
       *        'self',
       *        // Allow loading from our assets domain.  Notice the difference between * and **.
       *        'http://srv*.assets.example.com/**']);
       *
       *      // The blacklist overrides the whitelist so the open redirect here is blocked.
       *      $sceDelegateProvider.resourceUrlBlacklist([
       *        'http://myapp.example.com/clickThru**']);
       *      });
       * 
      */ function $SceDelegateProvider() { this.SCE_CONTEXTS = SCE_CONTEXTS; // Resource URLs can also be trusted by policy. var resourceUrlWhitelist = ['self'], resourceUrlBlacklist = []; /** * @ngdoc method * @name $sceDelegateProvider#resourceUrlWhitelist * @function * * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value * provided. This must be an array or null. A snapshot of this array is used so further * changes to the array are ignored. * * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items * allowed in this array. * * Note: **an empty whitelist array will block all URLs**! * * @return {Array} the currently set whitelist array. * * The **default value** when no whitelist has been explicitly set is `['self']` allowing only * same origin resource requests. * * @description * Sets/Gets the whitelist of trusted resource URLs. */ this.resourceUrlWhitelist = function (value) { if (arguments.length) { resourceUrlWhitelist = adjustMatchers(value); } return resourceUrlWhitelist; }; /** * @ngdoc method * @name $sceDelegateProvider#resourceUrlBlacklist * @function * * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value * provided. This must be an array or null. A snapshot of this array is used so further * changes to the array are ignored. * * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items * allowed in this array. * * The typical usage for the blacklist is to **block * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as * these would otherwise be trusted but actually return content from the redirected domain. * * Finally, **the blacklist overrides the whitelist** and has the final say. * * @return {Array} the currently set blacklist array. * * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there * is no blacklist.) * * @description * Sets/Gets the blacklist of trusted resource URLs. */ this.resourceUrlBlacklist = function (value) { if (arguments.length) { resourceUrlBlacklist = adjustMatchers(value); } return resourceUrlBlacklist; }; this.$get = ['$injector', function($injector) { var htmlSanitizer = function htmlSanitizer(html) { throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); }; if ($injector.has('$sanitize')) { htmlSanitizer = $injector.get('$sanitize'); } function matchUrl(matcher, parsedUrl) { if (matcher === 'self') { return urlIsSameOrigin(parsedUrl); } else { // definitely a regex. See adjustMatchers() return !!matcher.exec(parsedUrl.href); } } function isResourceUrlAllowedByPolicy(url) { var parsedUrl = urlResolve(url.toString()); var i, n, allowed = false; // Ensure that at least one item from the whitelist allows this url. for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { allowed = true; break; } } if (allowed) { // Ensure that no item from the blacklist blocked this url. for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { allowed = false; break; } } } return allowed; } function generateHolderType(Base) { var holderType = function TrustedValueHolderType(trustedValue) { this.$$unwrapTrustedValue = function() { return trustedValue; }; }; if (Base) { holderType.prototype = new Base(); } holderType.prototype.valueOf = function sceValueOf() { return this.$$unwrapTrustedValue(); }; holderType.prototype.toString = function sceToString() { return this.$$unwrapTrustedValue().toString(); }; return holderType; } var trustedValueHolderBase = generateHolderType(), byType = {}; byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); /** * @ngdoc method * @name $sceDelegate#trustAs * * @description * Returns an object that is trusted by angular for use in specified strict * contextual escaping contexts (such as ng-bind-html, ng-include, any src * attribute interpolation, any dom event binding attribute interpolation * such as for onclick, etc.) that uses the provided value. * See {@link ng.$sce $sce} for enabling strict contextual escaping. * * @param {string} type The kind of context in which this value is safe for use. e.g. url, * resourceUrl, html, js and css. * @param {*} value The value that that should be considered trusted/safe. * @returns {*} A value that can be used to stand in for the provided `value` in places * where Angular expects a $sce.trustAs() return value. */ function trustAs(type, trustedValue) { var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); if (!Constructor) { throw $sceMinErr('icontext', 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', type, trustedValue); } if (trustedValue === null || trustedValue === undefined || trustedValue === '') { return trustedValue; } // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting // mutable objects, we ensure here that the value passed in is actually a string. if (typeof trustedValue !== 'string') { throw $sceMinErr('itype', 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', type); } return new Constructor(trustedValue); } /** * @ngdoc method * @name $sceDelegate#valueOf * * @description * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. * * If the passed parameter is not a value that had been returned by {@link * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. * * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} * call or anything else. * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns * `value` unchanged. */ function valueOf(maybeTrusted) { if (maybeTrusted instanceof trustedValueHolderBase) { return maybeTrusted.$$unwrapTrustedValue(); } else { return maybeTrusted; } } /** * @ngdoc method * @name $sceDelegate#getTrusted * * @description * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and * returns the originally supplied value if the queried context type is a supertype of the * created type. If this condition isn't satisfied, throws an exception. * * @param {string} type The kind of context in which this value is to be used. * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs * `$sceDelegate.trustAs`} call. * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. */ function getTrusted(type, maybeTrusted) { if (maybeTrusted === null || maybeTrusted === undefined || maybeTrusted === '') { return maybeTrusted; } var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); if (constructor && maybeTrusted instanceof constructor) { return maybeTrusted.$$unwrapTrustedValue(); } // If we get here, then we may only take one of two actions. // 1. sanitize the value for the requested type, or // 2. throw an exception. if (type === SCE_CONTEXTS.RESOURCE_URL) { if (isResourceUrlAllowedByPolicy(maybeTrusted)) { return maybeTrusted; } else { throw $sceMinErr('insecurl', 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', maybeTrusted.toString()); } } else if (type === SCE_CONTEXTS.HTML) { return htmlSanitizer(maybeTrusted); } throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); } return { trustAs: trustAs, getTrusted: getTrusted, valueOf: valueOf }; }]; } /** * @ngdoc provider * @name $sceProvider * @description * * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. * - enable/disable Strict Contextual Escaping (SCE) in a module * - override the default implementation with a custom delegate * * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. */ /* jshint maxlen: false*/ /** * @ngdoc service * @name $sce * @function * * @description * * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. * * # Strict Contextual Escaping * * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain * contexts to result in a value that is marked as safe to use for that context. One example of * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer * to these contexts as privileged or SCE contexts. * * As of version 1.2, Angular ships with SCE enabled by default. * * Note: When enabled (the default), IE8 in quirks mode is not supported. In this mode, IE8 allows * one to execute arbitrary javascript by the use of the expression() syntax. Refer * to learn more about them. * You can ensure your document is in standards mode and not quirks mode by adding `` * to the top of your HTML document. * * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. * * Here's an example of a binding in a privileged context: * *
       *     
       *     
      *
      * * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE * disabled, this application allows the user to render arbitrary HTML into the DIV. * In a more realistic example, one may be rendering user comments, blog articles, etc. via * bindings. (HTML is just one example of a context where rendering user controlled input creates * security vulnerabilities.) * * For the case of HTML, you might use a library, either on the client side, or on the server side, * to sanitize unsafe HTML before binding to the value and rendering it in the document. * * How would you ensure that every place that used these types of bindings was bound to a value that * was sanitized by your library (or returned as safe for rendering by your server?) How can you * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some * properties/fields and forgot to update the binding to the sanitized value? * * To be secure by default, you want to ensure that any such bindings are disallowed unless you can * determine that something explicitly says it's safe to use a value for binding in that * context. You can then audit your code (a simple grep would do) to ensure that this is only done * for those values that you can easily tell are safe - because they were received from your server, * sanitized by your library, etc. You can organize your codebase to help with this - perhaps * allowing only the files in a specific directory to do this. Ensuring that the internal API * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. * * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to * obtain values that will be accepted by SCE / privileged contexts. * * * ## How does it work? * * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link * ng.$sce#parse $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. * * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly * simplified): * *
       *   var ngBindHtmlDirective = ['$sce', function($sce) {
       *     return function(scope, element, attr) {
       *       scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) {
       *         element.html(value || '');
       *       });
       *     };
       *   }];
       * 
      * * ## Impact on loading templates * * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as * `templateUrl`'s specified by {@link guide/directive directives}. * * By default, Angular only loads templates from the same domain and protocol as the application * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or * protocols, you may either either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. * * *Please note*: * The browser's * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) * policy apply in addition to this and may further restrict whether the template is successfully * loaded. This means that without the right CORS policy, loading templates from a different domain * won't work on all browsers. Also, loading templates from `file://` URL does not work on some * browsers. * * ## This feels like too much overhead * * It's important to remember that SCE only applies to interpolation expressions. * * If your expressions are constant literals, they're automatically trusted and you don't need to * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. * `
      `) just works. * * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. * * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load * templates in `ng-include` from your application's domain without having to even know about SCE. * It blocks loading templates from other domains or loading templates over http from an https * served document. You can change these by setting your own custom {@link * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. * * This significantly reduces the overhead. It is far easier to pay the small overhead and have an * application that's secure and can be audited to verify that with much more ease than bolting * security onto an application later. * * * ## What trusted context types are supported? * * | Context | Notes | * |---------------------|----------------| * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. If an unsafe value is encountered and the {@link ngSanitize $sanitize} module is present this will sanitize the value instead of throwing an error. | * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`
      Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. | * | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently unused. Feel free to use it in your own directives. | * * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist}
      * * Each element in these arrays must be one of the following: * * - **'self'** * - The special **string**, `'self'`, can be used to match against all URLs of the **same * domain** as the application document using the **same protocol**. * - **String** (except the special value `'self'`) * - The string is matched against the full *normalized / absolute URL* of the resource * being tested (substring matches are not good enough.) * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters * match themselves. * - `*`: matches zero or more occurrences of any character other than one of the following 6 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and ';'. It's a useful wildcard for use * in a whitelist. * - `**`: matches zero or more occurrences of *any* character. As such, it's not * not appropriate to use in for a scheme, domain, etc. as it would match too much. (e.g. * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might * not have been the intention.) It's usage at the very end of the path is ok. (e.g. * http://foo.example.com/templates/**). * - **RegExp** (*see caveat below*) * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to * accidentally introduce a bug when one updates a complex expression (imho, all regexes should * have good test coverage.). For instance, the use of `.` in the regex is correct only in a * small number of cases. A `.` character in the regex used when matching the scheme or a * subdomain could be matched against a `:` or literal `.` that was likely not intended. It * is highly recommended to use the string patterns and only fall back to regular expressions * if they as a last resort. * - The regular expression must be an instance of RegExp (i.e. not a string.) It is * matched against the **entire** *normalized / absolute URL* of the resource being tested * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags * present on the RegExp (such as multiline, global, ignoreCase) are ignored. * - If you are generating your JavaScript from some other templating engine (not * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), * remember to escape your regular expression (and be aware that you might need more than * one level of escaping depending on your templating engine and the way you interpolated * the value.) Do make use of your platform's escaping mechanism as it might be good * enough before coding your own. e.g. Ruby has * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). * Javascript lacks a similar built in function for escaping. Take a look at Google * Closure library's [goog.string.regExpEscape(s)]( * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). * * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. * * ## Show me an example using SCE. * * @example


      User comments
      By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when $sanitize is available. If $sanitize isn't available, this results in an error instead of an exploit.
      {{userComment.name}}:
      var mySceApp = angular.module('mySceApp', ['ngSanitize']); mySceApp.controller("myAppController", function myAppController($http, $templateCache, $sce) { var self = this; $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { self.userComments = userComments; }); self.explicitlyTrustedHtml = $sce.trustAsHtml( 'Hover over this text.'); }); [ { "name": "Alice", "htmlComment": "Is anyone reading this?" }, { "name": "Bob", "htmlComment": "Yes! Am I the only other one?" } ] describe('SCE doc demo', function() { it('should sanitize untrusted values', function() { expect(element(by.css('.htmlComment')).getInnerHtml()) .toBe('Is anyone reading this?'); }); it('should NOT sanitize explicitly trusted values', function() { expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe( 'Hover over this text.'); }); });
      * * * * ## Can I disable SCE completely? * * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits * for little coding overhead. It will be much harder to take an SCE disabled application and * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE * for cases where you have a lot of existing code that was written before SCE was introduced and * you're migrating them a module at a time. * * That said, here's how you can completely disable SCE: * *
       *   angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
       *     // Completely disable SCE.  For demonstration purposes only!
       *     // Do not use in new projects.
       *     $sceProvider.enabled(false);
       *   });
       * 
      * */ /* jshint maxlen: 100 */ function $SceProvider() { var enabled = true; /** * @ngdoc method * @name $sceProvider#enabled * @function * * @param {boolean=} value If provided, then enables/disables SCE. * @return {boolean} true if SCE is enabled, false otherwise. * * @description * Enables/disables SCE and returns the current value. */ this.enabled = function (value) { if (arguments.length) { enabled = !!value; } return enabled; }; /* Design notes on the default implementation for SCE. * * The API contract for the SCE delegate * ------------------------------------- * The SCE delegate object must provide the following 3 methods: * * - trustAs(contextEnum, value) * This method is used to tell the SCE service that the provided value is OK to use in the * contexts specified by contextEnum. It must return an object that will be accepted by * getTrusted() for a compatible contextEnum and return this value. * * - valueOf(value) * For values that were not produced by trustAs(), return them as is. For values that were * produced by trustAs(), return the corresponding input value to trustAs. Basically, if * trustAs is wrapping the given values into some type, this operation unwraps it when given * such a value. * * - getTrusted(contextEnum, value) * This function should return the a value that is safe to use in the context specified by * contextEnum or throw and exception otherwise. * * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be * opaque or wrapped in some holder object. That happens to be an implementation detail. For * instance, an implementation could maintain a registry of all trusted objects by context. In * such a case, trustAs() would return the same object that was passed in. getTrusted() would * return the same object passed in if it was found in the registry under a compatible context or * throw an exception otherwise. An implementation might only wrap values some of the time based * on some criteria. getTrusted() might return a value and not throw an exception for special * constants or objects even if not wrapped. All such implementations fulfill this contract. * * * A note on the inheritance model for SCE contexts * ------------------------------------------------ * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This * is purely an implementation details. * * The contract is simply this: * * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) * will also succeed. * * Inheritance happens to capture this in a natural way. In some future, we * may not use inheritance anymore. That is OK because no code outside of * sce.js and sceSpecs.js would need to be aware of this detail. */ this.$get = ['$parse', '$sniffer', '$sceDelegate', function( $parse, $sniffer, $sceDelegate) { // Prereq: Ensure that we're not running in IE8 quirks mode. In that mode, IE allows // the "expression(javascript expression)" syntax which is insecure. if (enabled && $sniffer.msie && $sniffer.msieDocumentMode < 8) { throw $sceMinErr('iequirks', 'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' + 'mode. You can fix this by adding the text to the top of your HTML ' + 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); } var sce = copy(SCE_CONTEXTS); /** * @ngdoc method * @name $sce#isEnabled * @function * * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. * * @description * Returns a boolean indicating if SCE is enabled. */ sce.isEnabled = function () { return enabled; }; sce.trustAs = $sceDelegate.trustAs; sce.getTrusted = $sceDelegate.getTrusted; sce.valueOf = $sceDelegate.valueOf; if (!enabled) { sce.trustAs = sce.getTrusted = function(type, value) { return value; }; sce.valueOf = identity; } /** * @ngdoc method * @name $sce#parse * * @description * Converts Angular {@link guide/expression expression} into a function. This is like {@link * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, * *result*)} * * @param {string} type The kind of SCE context in which this result will be used. * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: * * * `context` – `{object}` – an object against which any expressions embedded in the strings * are evaluated against (typically a scope object). * * `locals` – `{object=}` – local variables context object, useful for overriding values in * `context`. */ sce.parseAs = function sceParseAs(type, expr) { var parsed = $parse(expr); if (parsed.literal && parsed.constant) { return parsed; } else { return function sceParseAsTrusted(self, locals) { var result = sce.getTrusted(type, parsed(self, locals)); sceParseAsTrusted.$$unwatch = parsed.$$unwatch; return result; }; } }; /** * @ngdoc method * @name $sce#trustAs * * @description * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, * returns an object that is trusted by angular for use in specified strict contextual * escaping contexts (such as ng-bind-html, ng-include, any src attribute * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual * escaping. * * @param {string} type The kind of context in which this value is safe for use. e.g. url, * resource_url, html, js and css. * @param {*} value The value that that should be considered trusted/safe. * @returns {*} A value that can be used to stand in for the provided `value` in places * where Angular expects a $sce.trustAs() return value. */ /** * @ngdoc method * @name $sce#trustAsHtml * * @description * Shorthand method. `$sce.trustAsHtml(value)` → * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} * * @param {*} value The value to trustAs. * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives * only accept expressions that are either literal constants or are the * return value of {@link ng.$sce#trustAs $sce.trustAs}.) */ /** * @ngdoc method * @name $sce#trustAsUrl * * @description * Shorthand method. `$sce.trustAsUrl(value)` → * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} * * @param {*} value The value to trustAs. * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives * only accept expressions that are either literal constants or are the * return value of {@link ng.$sce#trustAs $sce.trustAs}.) */ /** * @ngdoc method * @name $sce#trustAsResourceUrl * * @description * Shorthand method. `$sce.trustAsResourceUrl(value)` → * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} * * @param {*} value The value to trustAs. * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives * only accept expressions that are either literal constants or are the return * value of {@link ng.$sce#trustAs $sce.trustAs}.) */ /** * @ngdoc method * @name $sce#trustAsJs * * @description * Shorthand method. `$sce.trustAsJs(value)` → * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} * * @param {*} value The value to trustAs. * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives * only accept expressions that are either literal constants or are the * return value of {@link ng.$sce#trustAs $sce.trustAs}.) */ /** * @ngdoc method * @name $sce#getTrusted * * @description * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the * originally supplied value if the queried context type is a supertype of the created type. * If this condition isn't satisfied, throws an exception. * * @param {string} type The kind of context in which this value is to be used. * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} * call. * @returns {*} The value the was originally provided to * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context. * Otherwise, throws an exception. */ /** * @ngdoc method * @name $sce#getTrustedHtml * * @description * Shorthand method. `$sce.getTrustedHtml(value)` → * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} * * @param {*} value The value to pass to `$sce.getTrusted`. * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` */ /** * @ngdoc method * @name $sce#getTrustedCss * * @description * Shorthand method. `$sce.getTrustedCss(value)` → * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} * * @param {*} value The value to pass to `$sce.getTrusted`. * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` */ /** * @ngdoc method * @name $sce#getTrustedUrl * * @description * Shorthand method. `$sce.getTrustedUrl(value)` → * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} * * @param {*} value The value to pass to `$sce.getTrusted`. * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` */ /** * @ngdoc method * @name $sce#getTrustedResourceUrl * * @description * Shorthand method. `$sce.getTrustedResourceUrl(value)` → * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} * * @param {*} value The value to pass to `$sceDelegate.getTrusted`. * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` */ /** * @ngdoc method * @name $sce#getTrustedJs * * @description * Shorthand method. `$sce.getTrustedJs(value)` → * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} * * @param {*} value The value to pass to `$sce.getTrusted`. * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` */ /** * @ngdoc method * @name $sce#parseAsHtml * * @description * Shorthand method. `$sce.parseAsHtml(expression string)` → * {@link ng.$sce#parse `$sce.parseAs($sce.HTML, value)`} * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: * * * `context` – `{object}` – an object against which any expressions embedded in the strings * are evaluated against (typically a scope object). * * `locals` – `{object=}` – local variables context object, useful for overriding values in * `context`. */ /** * @ngdoc method * @name $sce#parseAsCss * * @description * Shorthand method. `$sce.parseAsCss(value)` → * {@link ng.$sce#parse `$sce.parseAs($sce.CSS, value)`} * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: * * * `context` – `{object}` – an object against which any expressions embedded in the strings * are evaluated against (typically a scope object). * * `locals` – `{object=}` – local variables context object, useful for overriding values in * `context`. */ /** * @ngdoc method * @name $sce#parseAsUrl * * @description * Shorthand method. `$sce.parseAsUrl(value)` → * {@link ng.$sce#parse `$sce.parseAs($sce.URL, value)`} * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: * * * `context` – `{object}` – an object against which any expressions embedded in the strings * are evaluated against (typically a scope object). * * `locals` – `{object=}` – local variables context object, useful for overriding values in * `context`. */ /** * @ngdoc method * @name $sce#parseAsResourceUrl * * @description * Shorthand method. `$sce.parseAsResourceUrl(value)` → * {@link ng.$sce#parse `$sce.parseAs($sce.RESOURCE_URL, value)`} * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: * * * `context` – `{object}` – an object against which any expressions embedded in the strings * are evaluated against (typically a scope object). * * `locals` – `{object=}` – local variables context object, useful for overriding values in * `context`. */ /** * @ngdoc method * @name $sce#parseAsJs * * @description * Shorthand method. `$sce.parseAsJs(value)` → * {@link ng.$sce#parse `$sce.parseAs($sce.JS, value)`} * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: * * * `context` – `{object}` – an object against which any expressions embedded in the strings * are evaluated against (typically a scope object). * * `locals` – `{object=}` – local variables context object, useful for overriding values in * `context`. */ // Shorthand delegations. var parse = sce.parseAs, getTrusted = sce.getTrusted, trustAs = sce.trustAs; forEach(SCE_CONTEXTS, function (enumValue, name) { var lName = lowercase(name); sce[camelCase("parse_as_" + lName)] = function (expr) { return parse(enumValue, expr); }; sce[camelCase("get_trusted_" + lName)] = function (value) { return getTrusted(enumValue, value); }; sce[camelCase("trust_as_" + lName)] = function (value) { return trustAs(enumValue, value); }; }); return sce; }]; } /** * !!! This is an undocumented "private" service !!! * * @name $sniffer * @requires $window * @requires $document * * @property {boolean} history Does the browser support html5 history api ? * @property {boolean} hashchange Does the browser support hashchange event ? * @property {boolean} transitions Does the browser support CSS transition events ? * @property {boolean} animations Does the browser support CSS animation events ? * * @description * This is very simple implementation of testing browser's features. */ function $SnifferProvider() { this.$get = ['$window', '$document', function($window, $document) { var eventSupport = {}, android = int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), boxee = /Boxee/i.test(($window.navigator || {}).userAgent), document = $document[0] || {}, documentMode = document.documentMode, vendorPrefix, vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/, bodyStyle = document.body && document.body.style, transitions = false, animations = false, match; if (bodyStyle) { for(var prop in bodyStyle) { if(match = vendorRegex.exec(prop)) { vendorPrefix = match[0]; vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); break; } } if(!vendorPrefix) { vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; } transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); if (android && (!transitions||!animations)) { transitions = isString(document.body.style.webkitTransition); animations = isString(document.body.style.webkitAnimation); } } return { // Android has history.pushState, but it does not update location correctly // so let's not use the history API at all. // http://code.google.com/p/android/issues/detail?id=17471 // https://github.com/angular/angular.js/issues/904 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has // so let's not use the history API also // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined // jshint -W018 history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee), // jshint +W018 hashchange: 'onhashchange' in $window && // IE8 compatible mode lies (!documentMode || documentMode > 7), hasEvent: function(event) { // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have // it. In particular the event is not fired when backspace or delete key are pressed or // when cut operation is performed. if (event == 'input' && msie == 9) return false; if (isUndefined(eventSupport[event])) { var divElm = document.createElement('div'); eventSupport[event] = 'on' + event in divElm; } return eventSupport[event]; }, csp: csp(), vendorPrefix: vendorPrefix, transitions : transitions, animations : animations, android: android, msie : msie, msieDocumentMode: documentMode }; }]; } function $TimeoutProvider() { this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler', function($rootScope, $browser, $q, $exceptionHandler) { var deferreds = {}; /** * @ngdoc service * @name $timeout * * @description * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch * block and delegates any exceptions to * {@link ng.$exceptionHandler $exceptionHandler} service. * * The return value of registering a timeout function is a promise, which will be resolved when * the timeout is reached and the timeout function is executed. * * To cancel a timeout request, call `$timeout.cancel(promise)`. * * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to * synchronously flush the queue of deferred functions. * * @param {function()} fn A function, whose execution should be delayed. * @param {number=} [delay=0] Delay in milliseconds. * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this * promise will be resolved with is the return value of the `fn` function. * */ function timeout(fn, delay, invokeApply) { var deferred = $q.defer(), promise = deferred.promise, skipApply = (isDefined(invokeApply) && !invokeApply), timeoutId; timeoutId = $browser.defer(function() { try { deferred.resolve(fn()); } catch(e) { deferred.reject(e); $exceptionHandler(e); } finally { delete deferreds[promise.$$timeoutId]; } if (!skipApply) $rootScope.$apply(); }, delay); promise.$$timeoutId = timeoutId; deferreds[timeoutId] = deferred; return promise; } /** * @ngdoc method * @name $timeout#cancel * * @description * Cancels a task associated with the `promise`. As a result of this, the promise will be * resolved with a rejection. * * @param {Promise=} promise Promise returned by the `$timeout` function. * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully * canceled. */ timeout.cancel = function(promise) { if (promise && promise.$$timeoutId in deferreds) { deferreds[promise.$$timeoutId].reject('canceled'); delete deferreds[promise.$$timeoutId]; return $browser.defer.cancel(promise.$$timeoutId); } return false; }; return timeout; }]; } // NOTE: The usage of window and document instead of $window and $document here is // deliberate. This service depends on the specific behavior of anchor nodes created by the // browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and // cause us to break tests. In addition, when the browser resolves a URL for XHR, it // doesn't know about mocked locations and resolves URLs to the real document - which is // exactly the behavior needed here. There is little value is mocking these out for this // service. var urlParsingNode = document.createElement("a"); var originUrl = urlResolve(window.location.href, true); /** * * Implementation Notes for non-IE browsers * ---------------------------------------- * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, * results both in the normalizing and parsing of the URL. Normalizing means that a relative * URL will be resolved into an absolute URL in the context of the application document. * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related * properties are all populated to reflect the normalized URL. This approach has wide * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html * * Implementation Notes for IE * --------------------------- * IE >= 8 and <= 10 normalizes the URL when assigned to the anchor node similar to the other * browsers. However, the parsed components will not be set if the URL assigned did not specify * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We * work around that by performing the parsing in a 2nd step by taking a previously normalized * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the * properties such as protocol, hostname, port, etc. * * IE7 does not normalize the URL when assigned to an anchor node. (Apparently, it does, if one * uses the inner HTML approach to assign the URL as part of an HTML snippet - * http://stackoverflow.com/a/472729) However, setting img[src] does normalize the URL. * Unfortunately, setting img[src] to something like "javascript:foo" on IE throws an exception. * Since the primary usage for normalizing URLs is to sanitize such URLs, we can't use that * method and IE < 8 is unsupported. * * References: * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html * http://url.spec.whatwg.org/#urlutils * https://github.com/angular/angular.js/pull/2902 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ * * @function * @param {string} url The URL to be parsed. * @description Normalizes and parses a URL. * @returns {object} Returns the normalized URL as a dictionary. * * | member name | Description | * |---------------|----------------| * | href | A normalized version of the provided URL if it was not an absolute URL | * | protocol | The protocol including the trailing colon | * | host | The host and port (if the port is non-default) of the normalizedUrl | * | search | The search params, minus the question mark | * | hash | The hash string, minus the hash symbol * | hostname | The hostname * | port | The port, without ":" * | pathname | The pathname, beginning with "/" * */ function urlResolve(url, base) { var href = url; if (msie) { // Normalize before parse. Refer Implementation Notes on why this is // done in two steps on IE. urlParsingNode.setAttribute("href", href); href = urlParsingNode.href; } urlParsingNode.setAttribute('href', href); // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils return { href: urlParsingNode.href, protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', host: urlParsingNode.host, search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', hostname: urlParsingNode.hostname, port: urlParsingNode.port, pathname: (urlParsingNode.pathname.charAt(0) === '/') ? urlParsingNode.pathname : '/' + urlParsingNode.pathname }; } /** * Parse a request URL and determine whether this is a same-origin request as the application document. * * @param {string|object} requestUrl The url of the request as a string that will be resolved * or a parsed URL object. * @returns {boolean} Whether the request is for the same origin as the application document. */ function urlIsSameOrigin(requestUrl) { var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; return (parsed.protocol === originUrl.protocol && parsed.host === originUrl.host); } /** * @ngdoc service * @name $window * * @description * A reference to the browser's `window` object. While `window` * is globally available in JavaScript, it causes testability problems, because * it is a global variable. In angular we always refer to it through the * `$window` service, so it may be overridden, removed or mocked for testing. * * Expressions, like the one defined for the `ngClick` directive in the example * below, are evaluated with respect to the current scope. Therefore, there is * no risk of inadvertently coding in a dependency on a global value in such an * expression. * * @example
      it('should display the greeting in the input box', function() { element(by.model('greeting')).sendKeys('Hello, E2E Tests'); // If we click the button it will block the test runner // element(':button').click(); });
      */ function $WindowProvider(){ this.$get = valueFn(window); } /** * @ngdoc provider * @name $filterProvider * @description * * Filters are just functions which transform input to an output. However filters need to be * Dependency Injected. To achieve this a filter definition consists of a factory function which is * annotated with dependencies and is responsible for creating a filter function. * * ```js * // Filter registration * function MyModule($provide, $filterProvider) { * // create a service to demonstrate injection (not always needed) * $provide.value('greet', function(name){ * return 'Hello ' + name + '!'; * }); * * // register a filter factory which uses the * // greet service to demonstrate DI. * $filterProvider.register('greet', function(greet){ * // return the filter function which uses the greet service * // to generate salutation * return function(text) { * // filters need to be forgiving so check input validity * return text && greet(text) || text; * }; * }); * } * ``` * * The filter function is registered with the `$injector` under the filter name suffix with * `Filter`. * * ```js * it('should be the same instance', inject( * function($filterProvider) { * $filterProvider.register('reverse', function(){ * return ...; * }); * }, * function($filter, reverseFilter) { * expect($filter('reverse')).toBe(reverseFilter); * }); * ``` * * * For more information about how angular filters work, and how to create your own filters, see * {@link guide/filter Filters} in the Angular Developer Guide. */ /** * @ngdoc method * @name $filterProvider#register * @description * Register filter factory function. * * @param {String} name Name of the filter. * @param {Function} fn The filter factory function which is injectable. */ /** * @ngdoc service * @name $filter * @function * @description * Filters are used for formatting data displayed to the user. * * The general syntax in templates is as follows: * * {{ expression [| filter_name[:parameter_value] ... ] }} * * @param {String} name Name of the filter function to retrieve * @return {Function} the filter function * @example

      {{ originalText }}

      {{ filteredText }}

      angular.module('filterExample', []) .controller('MainCtrl', function($scope, $filter) { $scope.originalText = 'hello'; $scope.filteredText = $filter('uppercase')($scope.originalText); });
      */ $FilterProvider.$inject = ['$provide']; function $FilterProvider($provide) { var suffix = 'Filter'; /** * @ngdoc method * @name $controllerProvider#register * @param {string|Object} name Name of the filter function, or an object map of filters where * the keys are the filter names and the values are the filter factories. * @returns {Object} Registered filter instance, or if a map of filters was provided then a map * of the registered filter instances. */ function register(name, factory) { if(isObject(name)) { var filters = {}; forEach(name, function(filter, key) { filters[key] = register(key, filter); }); return filters; } else { return $provide.factory(name + suffix, factory); } } this.register = register; this.$get = ['$injector', function($injector) { return function(name) { return $injector.get(name + suffix); }; }]; //////////////////////////////////////// /* global currencyFilter: false, dateFilter: false, filterFilter: false, jsonFilter: false, limitToFilter: false, lowercaseFilter: false, numberFilter: false, orderByFilter: false, uppercaseFilter: false, */ register('currency', currencyFilter); register('date', dateFilter); register('filter', filterFilter); register('json', jsonFilter); register('limitTo', limitToFilter); register('lowercase', lowercaseFilter); register('number', numberFilter); register('orderBy', orderByFilter); register('uppercase', uppercaseFilter); } /** * @ngdoc filter * @name filter * @function * * @description * Selects a subset of items from `array` and returns it as a new array. * * @param {Array} array The source array. * @param {string|Object|function()} expression The predicate to be used for selecting items from * `array`. * * Can be one of: * * - `string`: The string is evaluated as an expression and the resulting value is used for substring match against * the contents of the `array`. All strings or objects with string properties in `array` that contain this string * will be returned. The predicate can be negated by prefixing the string with `!`. * * - `Object`: A pattern object can be used to filter specific properties on objects contained * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items * which have property `name` containing "M" and property `phone` containing "1". A special * property name `$` can be used (as in `{$:"text"}`) to accept a match against any * property of the object. That's equivalent to the simple substring match with a `string` * as described above. * * - `function(value)`: A predicate function can be used to write arbitrary filters. The function is * called for each element of `array`. The final result is an array of those elements that * the predicate returned true for. * * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in * determining if the expected value (from the filter expression) and actual value (from * the object in the array) should be considered a match. * * Can be one of: * * - `function(actual, expected)`: * The function will be given the object value and the predicate value to compare and * should return true if the item should be included in filtered result. * * - `true`: A shorthand for `function(actual, expected) { return angular.equals(expected, actual)}`. * this is essentially strict comparison of expected and actual. * * - `false|undefined`: A short hand for a function which will look for a substring match in case * insensitive way. * * @example
      Search:
      NamePhone
      {{friend.name}} {{friend.phone}}

      Any:
      Name only
      Phone only
      Equality
      NamePhone
      {{friendObj.name}} {{friendObj.phone}}
      var expectFriendNames = function(expectedNames, key) { element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { arr.forEach(function(wd, i) { expect(wd.getText()).toMatch(expectedNames[i]); }); }); }; it('should search across all fields when filtering with a string', function() { var searchText = element(by.model('searchText')); searchText.clear(); searchText.sendKeys('m'); expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); searchText.clear(); searchText.sendKeys('76'); expectFriendNames(['John', 'Julie'], 'friend'); }); it('should search in specific fields when filtering with a predicate object', function() { var searchAny = element(by.model('search.$')); searchAny.clear(); searchAny.sendKeys('i'); expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); }); it('should use a equal comparison when comparator is true', function() { var searchName = element(by.model('search.name')); var strict = element(by.model('strict')); searchName.clear(); searchName.sendKeys('Julie'); strict.click(); expectFriendNames(['Julie'], 'friendObj'); });
      */ function filterFilter() { return function(array, expression, comparator) { if (!isArray(array)) return array; var comparatorType = typeof(comparator), predicates = []; predicates.check = function(value) { for (var j = 0; j < predicates.length; j++) { if(!predicates[j](value)) { return false; } } return true; }; if (comparatorType !== 'function') { if (comparatorType === 'boolean' && comparator) { comparator = function(obj, text) { return angular.equals(obj, text); }; } else { comparator = function(obj, text) { if (obj && text && typeof obj === 'object' && typeof text === 'object') { for (var objKey in obj) { if (objKey.charAt(0) !== '$' && hasOwnProperty.call(obj, objKey) && comparator(obj[objKey], text[objKey])) { return true; } } return false; } text = (''+text).toLowerCase(); return (''+obj).toLowerCase().indexOf(text) > -1; }; } } var search = function(obj, text){ if (typeof text == 'string' && text.charAt(0) === '!') { return !search(obj, text.substr(1)); } switch (typeof obj) { case "boolean": case "number": case "string": return comparator(obj, text); case "object": switch (typeof text) { case "object": return comparator(obj, text); default: for ( var objKey in obj) { if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) { return true; } } break; } return false; case "array": for ( var i = 0; i < obj.length; i++) { if (search(obj[i], text)) { return true; } } return false; default: return false; } }; switch (typeof expression) { case "boolean": case "number": case "string": // Set up expression object and fall through expression = {$:expression}; // jshint -W086 case "object": // jshint +W086 for (var key in expression) { (function(path) { if (typeof expression[path] == 'undefined') return; predicates.push(function(value) { return search(path == '$' ? value : (value && value[path]), expression[path]); }); })(key); } break; case 'function': predicates.push(expression); break; default: return array; } var filtered = []; for ( var j = 0; j < array.length; j++) { var value = array[j]; if (predicates.check(value)) { filtered.push(value); } } return filtered; }; } /** * @ngdoc filter * @name currency * @function * * @description * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default * symbol for current locale is used. * * @param {number} amount Input to filter. * @param {string=} symbol Currency symbol or identifier to be displayed. * @returns {string} Formatted number. * * * @example

      default currency symbol ($): {{amount | currency}}
      custom currency identifier (USD$): {{amount | currency:"USD$"}}
      it('should init with 1234.56', function() { expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('USD$1,234.56'); }); it('should update', function() { if (browser.params.browser == 'safari') { // Safari does not understand the minus key. See // https://github.com/angular/protractor/issues/481 return; } element(by.model('amount')).clear(); element(by.model('amount')).sendKeys('-1234'); expect(element(by.id('currency-default')).getText()).toBe('($1,234.00)'); expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('(USD$1,234.00)'); });
      */ currencyFilter.$inject = ['$locale']; function currencyFilter($locale) { var formats = $locale.NUMBER_FORMATS; return function(amount, currencySymbol){ if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM; return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2). replace(/\u00A4/g, currencySymbol); }; } /** * @ngdoc filter * @name number * @function * * @description * Formats a number as text. * * If the input is not a number an empty string is returned. * * @param {number|string} number Number to format. * @param {(number|string)=} fractionSize Number of decimal places to round the number to. * If this is not provided then the fraction size is computed from the current locale's number * formatting pattern. In the case of the default locale, it will be 3. * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit. * * @example
      Enter number:
      Default formatting: {{val | number}}
      No fractions: {{val | number:0}}
      Negative number: {{-val | number:4}}
      it('should format numbers', function() { expect(element(by.id('number-default')).getText()).toBe('1,234.568'); expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); }); it('should update', function() { element(by.model('val')).clear(); element(by.model('val')).sendKeys('3374.333'); expect(element(by.id('number-default')).getText()).toBe('3,374.333'); expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); });
      */ numberFilter.$inject = ['$locale']; function numberFilter($locale) { var formats = $locale.NUMBER_FORMATS; return function(number, fractionSize) { return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize); }; } var DECIMAL_SEP = '.'; function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { if (number == null || !isFinite(number) || isObject(number)) return ''; var isNegative = number < 0; number = Math.abs(number); var numStr = number + '', formatedText = '', parts = []; var hasExponent = false; if (numStr.indexOf('e') !== -1) { var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); if (match && match[2] == '-' && match[3] > fractionSize + 1) { numStr = '0'; } else { formatedText = numStr; hasExponent = true; } } if (!hasExponent) { var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length; // determine fractionSize if it is not specified if (isUndefined(fractionSize)) { fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac); } var pow = Math.pow(10, fractionSize + 1); number = Math.floor(number * pow + 5) / pow; var fraction = ('' + number).split(DECIMAL_SEP); var whole = fraction[0]; fraction = fraction[1] || ''; var i, pos = 0, lgroup = pattern.lgSize, group = pattern.gSize; if (whole.length >= (lgroup + group)) { pos = whole.length - lgroup; for (i = 0; i < pos; i++) { if ((pos - i)%group === 0 && i !== 0) { formatedText += groupSep; } formatedText += whole.charAt(i); } } for (i = pos; i < whole.length; i++) { if ((whole.length - i)%lgroup === 0 && i !== 0) { formatedText += groupSep; } formatedText += whole.charAt(i); } // format fraction part. while(fraction.length < fractionSize) { fraction += '0'; } if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize); } else { if (fractionSize > 0 && number > -1 && number < 1) { formatedText = number.toFixed(fractionSize); } } parts.push(isNegative ? pattern.negPre : pattern.posPre); parts.push(formatedText); parts.push(isNegative ? pattern.negSuf : pattern.posSuf); return parts.join(''); } function padNumber(num, digits, trim) { var neg = ''; if (num < 0) { neg = '-'; num = -num; } num = '' + num; while(num.length < digits) num = '0' + num; if (trim) num = num.substr(num.length - digits); return neg + num; } function dateGetter(name, size, offset, trim) { offset = offset || 0; return function(date) { var value = date['get' + name](); if (offset > 0 || value > -offset) value += offset; if (value === 0 && offset == -12 ) value = 12; return padNumber(value, size, trim); }; } function dateStrGetter(name, shortForm) { return function(date, formats) { var value = date['get' + name](); var get = uppercase(shortForm ? ('SHORT' + name) : name); return formats[get][value]; }; } function timeZoneGetter(date) { var zone = -1 * date.getTimezoneOffset(); var paddedZone = (zone >= 0) ? "+" : ""; paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + padNumber(Math.abs(zone % 60), 2); return paddedZone; } function getFirstThursdayOfYear(year) { // 0 = index of January var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay(); // 4 = index of Thursday (+1 to account for 1st = 5) // 11 = index of *next* Thursday (+1 account for 1st = 12) return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst); } function getThursdayThisWeek(datetime) { return new Date(datetime.getFullYear(), datetime.getMonth(), // 4 = index of Thursday datetime.getDate() + (4 - datetime.getDay())); } function weekGetter(size) { return function(date) { var firstThurs = getFirstThursdayOfYear(date.getFullYear()), thisThurs = getThursdayThisWeek(date); var diff = +thisThurs - +firstThurs, result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week return padNumber(result, size); }; } function ampmGetter(date, formats) { return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; } var DATE_FORMATS = { yyyy: dateGetter('FullYear', 4), yy: dateGetter('FullYear', 2, 0, true), y: dateGetter('FullYear', 1), MMMM: dateStrGetter('Month'), MMM: dateStrGetter('Month', true), MM: dateGetter('Month', 2, 1), M: dateGetter('Month', 1, 1), dd: dateGetter('Date', 2), d: dateGetter('Date', 1), HH: dateGetter('Hours', 2), H: dateGetter('Hours', 1), hh: dateGetter('Hours', 2, -12), h: dateGetter('Hours', 1, -12), mm: dateGetter('Minutes', 2), m: dateGetter('Minutes', 1), ss: dateGetter('Seconds', 2), s: dateGetter('Seconds', 1), // while ISO 8601 requires fractions to be prefixed with `.` or `,` // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions sss: dateGetter('Milliseconds', 3), EEEE: dateStrGetter('Day'), EEE: dateStrGetter('Day', true), a: ampmGetter, Z: timeZoneGetter, ww: weekGetter(2), w: weekGetter(1) }; var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|w+))(.*)/, NUMBER_STRING = /^\-?\d+$/; /** * @ngdoc filter * @name date * @function * * @description * Formats `date` to a string based on the requested `format`. * * `format` string can be composed of the following elements: * * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) * * `'MMMM'`: Month in year (January-December) * * `'MMM'`: Month in year (Jan-Dec) * * `'MM'`: Month in year, padded (01-12) * * `'M'`: Month in year (1-12) * * `'dd'`: Day in month, padded (01-31) * * `'d'`: Day in month (1-31) * * `'EEEE'`: Day in Week,(Sunday-Saturday) * * `'EEE'`: Day in Week, (Sun-Sat) * * `'HH'`: Hour in day, padded (00-23) * * `'H'`: Hour in day (0-23) * * `'hh'`: Hour in am/pm, padded (01-12) * * `'h'`: Hour in am/pm, (1-12) * * `'mm'`: Minute in hour, padded (00-59) * * `'m'`: Minute in hour (0-59) * * `'ss'`: Second in minute, padded (00-59) * * `'s'`: Second in minute (0-59) * * `'.sss' or ',sss'`: Millisecond in second, padded (000-999) * * `'a'`: am/pm marker * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) * * `'ww'`: ISO-8601 week of year (00-53) * * `'w'`: ISO-8601 week of year (0-53) * * `format` string can also be one of the following predefined * {@link guide/i18n localizable formats}: * * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale * (e.g. Sep 3, 2010 12:05:08 pm) * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 pm) * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale * (e.g. Friday, September 3, 2010) * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm) * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm) * * `format` string can contain literal values. These need to be quoted with single quotes (e.g. * `"h 'in the morning'"`). In order to output single quote, use two single quotes in a sequence * (e.g. `"h 'o''clock'"`). * * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and its * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is * specified in the string input, the time is considered to be in the local timezone. * @param {string=} format Formatting rules (see Description). If not specified, * `mediumDate` is used. * @returns {string} Formatted string or the input if input is not recognized as date/millis. * * @example {{1288323623006 | date:'medium'}}: {{1288323623006 | date:'medium'}}
      {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}: {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}
      {{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}: {{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}
      it('should format date', function() { expect(element(by.binding("1288323623006 | date:'medium'")).getText()). toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); });
      */ dateFilter.$inject = ['$locale']; function dateFilter($locale) { var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; // 1 2 3 4 5 6 7 8 9 10 11 function jsonStringToDate(string) { var match; if (match = string.match(R_ISO8601_STR)) { var date = new Date(0), tzHour = 0, tzMin = 0, dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, timeSetter = match[8] ? date.setUTCHours : date.setHours; if (match[9]) { tzHour = int(match[9] + match[10]); tzMin = int(match[9] + match[11]); } dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3])); var h = int(match[4]||0) - tzHour; var m = int(match[5]||0) - tzMin; var s = int(match[6]||0); var ms = Math.round(parseFloat('0.' + (match[7]||0)) * 1000); timeSetter.call(date, h, m, s, ms); return date; } return string; } return function(date, format) { var text = '', parts = [], fn, match; format = format || 'mediumDate'; format = $locale.DATETIME_FORMATS[format] || format; if (isString(date)) { if (NUMBER_STRING.test(date)) { date = int(date); } else { date = jsonStringToDate(date); } } if (isNumber(date)) { date = new Date(date); } if (!isDate(date)) { return date; } while(format) { match = DATE_FORMATS_SPLIT.exec(format); if (match) { parts = concat(parts, match, 1); format = parts.pop(); } else { parts.push(format); format = null; } } forEach(parts, function(value){ fn = DATE_FORMATS[value]; text += fn ? fn(date, $locale.DATETIME_FORMATS) : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); }); return text; }; } /** * @ngdoc filter * @name json * @function * * @description * Allows you to convert a JavaScript object into JSON string. * * This filter is mostly useful for debugging. When using the double curly {{value}} notation * the binding is automatically converted to JSON. * * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. * @returns {string} JSON string. * * * @example
      {{ {'name':'value'} | json }}
      it('should jsonify filtered objects', function() { expect(element(by.binding("{'name':'value'}")).getText()).toMatch(/\{\n "name": ?"value"\n}/); });
      * */ function jsonFilter() { return function(object) { return toJson(object, true); }; } /** * @ngdoc filter * @name lowercase * @function * @description * Converts string to lowercase. * @see angular.lowercase */ var lowercaseFilter = valueFn(lowercase); /** * @ngdoc filter * @name uppercase * @function * @description * Converts string to uppercase. * @see angular.uppercase */ var uppercaseFilter = valueFn(uppercase); /** * @ngdoc filter * @name limitTo * @function * * @description * Creates a new array or string containing only a specified number of elements. The elements * are taken from either the beginning or the end of the source array or string, as specified by * the value and sign (positive or negative) of `limit`. * * @param {Array|string} input Source array or string to be limited. * @param {string|number} limit The length of the returned array or string. If the `limit` number * is positive, `limit` number of items from the beginning of the source array/string are copied. * If the number is negative, `limit` number of items from the end of the source array/string * are copied. The `limit` will be trimmed if it exceeds `array.length` * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array * had less than `limit` elements. * * @example
      Limit {{numbers}} to:

      Output numbers: {{ numbers | limitTo:numLimit }}

      Limit {{letters}} to:

      Output letters: {{ letters | limitTo:letterLimit }}

      var numLimitInput = element(by.model('numLimit')); var letterLimitInput = element(by.model('letterLimit')); var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); it('should limit the number array to first three items', function() { expect(numLimitInput.getAttribute('value')).toBe('3'); expect(letterLimitInput.getAttribute('value')).toBe('3'); expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); expect(limitedLetters.getText()).toEqual('Output letters: abc'); }); it('should update the output when -3 is entered', function() { numLimitInput.clear(); numLimitInput.sendKeys('-3'); letterLimitInput.clear(); letterLimitInput.sendKeys('-3'); expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); expect(limitedLetters.getText()).toEqual('Output letters: ghi'); }); it('should not exceed the maximum size of input array', function() { numLimitInput.clear(); numLimitInput.sendKeys('100'); letterLimitInput.clear(); letterLimitInput.sendKeys('100'); expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); });
      */ function limitToFilter(){ return function(input, limit) { if (!isArray(input) && !isString(input)) return input; if (Math.abs(Number(limit)) === Infinity) { limit = Number(limit); } else { limit = int(limit); } if (isString(input)) { //NaN check on limit if (limit) { return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length); } else { return ""; } } var out = [], i, n; // if abs(limit) exceeds maximum length, trim it if (limit > input.length) limit = input.length; else if (limit < -input.length) limit = -input.length; if (limit > 0) { i = 0; n = limit; } else { i = input.length + limit; n = input.length; } for (; i} expression A predicate to be * used by the comparator to determine the order of elements. * * Can be one of: * * - `function`: Getter function. The result of this function will be sorted using the * `<`, `=`, `>` operator. * - `string`: An Angular expression which evaluates to an object to order by, such as 'name' * to sort by a property called 'name'. Optionally prefixed with `+` or `-` to control * ascending or descending sort order (for example, +name or -name). * - `Array`: An array of function or string predicates. The first predicate in the array * is used for sorting, but when two items are equivalent, the next predicate is used. * * @param {boolean=} reverse Reverse the order of the array. * @returns {Array} Sorted copy of the source array. * * @example
      Sorting predicate = {{predicate}}; reverse = {{reverse}}

      [ unsorted ]
      Name (^) Phone Number Age
      {{friend.name}} {{friend.phone}} {{friend.age}}
      */ orderByFilter.$inject = ['$parse']; function orderByFilter($parse){ return function(array, sortPredicate, reverseOrder) { if (!isArray(array)) return array; if (!sortPredicate) return array; sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate]; sortPredicate = map(sortPredicate, function(predicate){ var descending = false, get = predicate || identity; if (isString(predicate)) { if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { descending = predicate.charAt(0) == '-'; predicate = predicate.substring(1); } get = $parse(predicate); if (get.constant) { var key = get(); return reverseComparator(function(a,b) { return compare(a[key], b[key]); }, descending); } } return reverseComparator(function(a,b){ return compare(get(a),get(b)); }, descending); }); var arrayCopy = []; for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); } return arrayCopy.sort(reverseComparator(comparator, reverseOrder)); function comparator(o1, o2){ for ( var i = 0; i < sortPredicate.length; i++) { var comp = sortPredicate[i](o1, o2); if (comp !== 0) return comp; } return 0; } function reverseComparator(comp, descending) { return toBoolean(descending) ? function(a,b){return comp(b,a);} : comp; } function compare(v1, v2){ var t1 = typeof v1; var t2 = typeof v2; if (t1 == t2) { if (t1 == "string") { v1 = v1.toLowerCase(); v2 = v2.toLowerCase(); } if (v1 === v2) return 0; return v1 < v2 ? -1 : 1; } else { return t1 < t2 ? -1 : 1; } } }; } function ngDirective(directive) { if (isFunction(directive)) { directive = { link: directive }; } directive.restrict = directive.restrict || 'AC'; return valueFn(directive); } /** * @ngdoc directive * @name a * @restrict E * * @description * Modifies the default behavior of the html A tag so that the default action is prevented when * the href attribute is empty. * * This change permits the easy creation of action links with the `ngClick` directive * without changing the location or causing page reloads, e.g.: * `Add Item` */ var htmlAnchorDirective = valueFn({ restrict: 'E', compile: function(element, attr) { if (msie <= 8) { // turn link into a stylable link in IE // but only if it doesn't have name attribute, in which case it's an anchor if (!attr.href && !attr.name) { attr.$set('href', ''); } // add a comment node to anchors to workaround IE bug that causes element content to be reset // to new attribute content if attribute is updated with value containing @ and element also // contains value with @ // see issue #1949 element.append(document.createComment('IE fix')); } if (!attr.href && !attr.xlinkHref && !attr.name) { return function(scope, element) { // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? 'xlink:href' : 'href'; element.on('click', function(event){ // if we have no href url, then don't navigate anywhere. if (!element.attr(href)) { event.preventDefault(); } }); }; } } }); /** * @ngdoc directive * @name ngHref * @restrict A * @priority 99 * * @description * Using Angular markup like `{{hash}}` in an href attribute will * make the link go to the wrong URL if the user clicks it before * Angular has a chance to replace the `{{hash}}` markup with its * value. Until Angular replaces the markup the link will be broken * and will most likely return a 404 error. * * The `ngHref` directive solves this problem. * * The wrong way to write it: * ```html * * ``` * * The correct way to write it: * ```html * * ``` * * @element A * @param {template} ngHref any string which can contain `{{}}` markup. * * @example * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes * in links and their different behaviors:
      link 1 (link, don't reload)
      link 2 (link, don't reload)
      link 3 (link, reload!)
      anchor (link, don't reload)
      anchor (no link)
      link (link, change location)
      it('should execute ng-click but not reload when href without value', function() { element(by.id('link-1')).click(); expect(element(by.model('value')).getAttribute('value')).toEqual('1'); expect(element(by.id('link-1')).getAttribute('href')).toBe(''); }); it('should execute ng-click but not reload when href empty string', function() { element(by.id('link-2')).click(); expect(element(by.model('value')).getAttribute('value')).toEqual('2'); expect(element(by.id('link-2')).getAttribute('href')).toBe(''); }); it('should execute ng-click and change url when ng-href specified', function() { expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); element(by.id('link-3')).click(); // At this point, we navigate away from an Angular page, so we need // to use browser.driver to get the base webdriver. browser.wait(function() { return browser.driver.getCurrentUrl().then(function(url) { return url.match(/\/123$/); }); }, 1000, 'page should navigate to /123'); }); xit('should execute ng-click but not reload when href empty string and name specified', function() { element(by.id('link-4')).click(); expect(element(by.model('value')).getAttribute('value')).toEqual('4'); expect(element(by.id('link-4')).getAttribute('href')).toBe(''); }); it('should execute ng-click but not reload when no href but name specified', function() { element(by.id('link-5')).click(); expect(element(by.model('value')).getAttribute('value')).toEqual('5'); expect(element(by.id('link-5')).getAttribute('href')).toBe(null); }); it('should only change url when only ng-href', function() { element(by.model('value')).clear(); element(by.model('value')).sendKeys('6'); expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); element(by.id('link-6')).click(); // At this point, we navigate away from an Angular page, so we need // to use browser.driver to get the base webdriver. browser.wait(function() { return browser.driver.getCurrentUrl().then(function(url) { return url.match(/\/6$/); }); }, 1000, 'page should navigate to /6'); }); */ /** * @ngdoc directive * @name ngSrc * @restrict A * @priority 99 * * @description * Using Angular markup like `{{hash}}` in a `src` attribute doesn't * work right: The browser will fetch from the URL with the literal * text `{{hash}}` until Angular replaces the expression inside * `{{hash}}`. The `ngSrc` directive solves this problem. * * The buggy way to write it: * ```html * * ``` * * The correct way to write it: * ```html * * ``` * * @element IMG * @param {template} ngSrc any string which can contain `{{}}` markup. */ /** * @ngdoc directive * @name ngSrcset * @restrict A * @priority 99 * * @description * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't * work right: The browser will fetch from the URL with the literal * text `{{hash}}` until Angular replaces the expression inside * `{{hash}}`. The `ngSrcset` directive solves this problem. * * The buggy way to write it: * ```html * * ``` * * The correct way to write it: * ```html * * ``` * * @element IMG * @param {template} ngSrcset any string which can contain `{{}}` markup. */ /** * @ngdoc directive * @name ngDisabled * @restrict A * @priority 100 * * @description * * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs: * ```html *
      * *
      * ``` * * The HTML specification does not require browsers to preserve the values of boolean attributes * such as disabled. (Their presence means true and their absence means false.) * If we put an Angular interpolation expression into such an attribute then the * binding information would be lost when the browser removes the attribute. * The `ngDisabled` directive solves this problem for the `disabled` attribute. * This complementary directive is not removed by the browser and so provides * a permanent reliable place to store the binding information. * * @example Click me to toggle:
      it('should toggle button', function() { expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy(); element(by.model('checked')).click(); expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); });
      * * @element INPUT * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, * then special attribute "disabled" will be set on the element */ /** * @ngdoc directive * @name ngChecked * @restrict A * @priority 100 * * @description * The HTML specification does not require browsers to preserve the values of boolean attributes * such as checked. (Their presence means true and their absence means false.) * If we put an Angular interpolation expression into such an attribute then the * binding information would be lost when the browser removes the attribute. * The `ngChecked` directive solves this problem for the `checked` attribute. * This complementary directive is not removed by the browser and so provides * a permanent reliable place to store the binding information. * @example Check me to check both:
      it('should check both checkBoxes', function() { expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy(); element(by.model('master')).click(); expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy(); });
      * * @element INPUT * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, * then special attribute "checked" will be set on the element */ /** * @ngdoc directive * @name ngReadonly * @restrict A * @priority 100 * * @description * The HTML specification does not require browsers to preserve the values of boolean attributes * such as readonly. (Their presence means true and their absence means false.) * If we put an Angular interpolation expression into such an attribute then the * binding information would be lost when the browser removes the attribute. * The `ngReadonly` directive solves this problem for the `readonly` attribute. * This complementary directive is not removed by the browser and so provides * a permanent reliable place to store the binding information. * @example Check me to make text readonly:
      it('should toggle readonly attr', function() { expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); element(by.model('checked')).click(); expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); });
      * * @element INPUT * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, * then special attribute "readonly" will be set on the element */ /** * @ngdoc directive * @name ngSelected * @restrict A * @priority 100 * * @description * The HTML specification does not require browsers to preserve the values of boolean attributes * such as selected. (Their presence means true and their absence means false.) * If we put an Angular interpolation expression into such an attribute then the * binding information would be lost when the browser removes the attribute. * The `ngSelected` directive solves this problem for the `selected` attribute. * This complementary directive is not removed by the browser and so provides * a permanent reliable place to store the binding information. * * @example Check me to select:
      it('should select Greetings!', function() { expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy(); element(by.model('selected')).click(); expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); });
      * * @element OPTION * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, * then special attribute "selected" will be set on the element */ /** * @ngdoc directive * @name ngOpen * @restrict A * @priority 100 * * @description * The HTML specification does not require browsers to preserve the values of boolean attributes * such as open. (Their presence means true and their absence means false.) * If we put an Angular interpolation expression into such an attribute then the * binding information would be lost when the browser removes the attribute. * The `ngOpen` directive solves this problem for the `open` attribute. * This complementary directive is not removed by the browser and so provides * a permanent reliable place to store the binding information. * @example Check me check multiple:
      Show/Hide me
      it('should toggle open', function() { expect(element(by.id('details')).getAttribute('open')).toBeFalsy(); element(by.model('open')).click(); expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); });
      * * @element DETAILS * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, * then special attribute "open" will be set on the element */ var ngAttributeAliasDirectives = {}; // boolean attrs are evaluated forEach(BOOLEAN_ATTR, function(propName, attrName) { // binding to multiple is not supported if (propName == "multiple") return; var normalized = directiveNormalize('ng-' + attrName); ngAttributeAliasDirectives[normalized] = function() { return { priority: 100, link: function(scope, element, attr) { scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { attr.$set(attrName, !!value); }); } }; }; }); // ng-src, ng-srcset, ng-href are interpolated forEach(['src', 'srcset', 'href'], function(attrName) { var normalized = directiveNormalize('ng-' + attrName); ngAttributeAliasDirectives[normalized] = function() { return { priority: 99, // it needs to run after the attributes are interpolated link: function(scope, element, attr) { var propName = attrName, name = attrName; if (attrName === 'href' && toString.call(element.prop('href')) === '[object SVGAnimatedString]') { name = 'xlinkHref'; attr.$attr[name] = 'xlink:href'; propName = null; } attr.$observe(normalized, function(value) { if (!value) return; attr.$set(name, value); // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need // to set the property as well to achieve the desired effect. // we use attr[attrName] value since $set can sanitize the url. if (msie && propName) element.prop(propName, attr[name]); }); } }; }; }); /* global -nullFormCtrl */ var nullFormCtrl = { $addControl: noop, $removeControl: noop, $setValidity: noop, $setDirty: noop, $setPristine: noop }; /** * @ngdoc type * @name form.FormController * * @property {boolean} $pristine True if user has not interacted with the form yet. * @property {boolean} $dirty True if user has already interacted with the form. * @property {boolean} $valid True if all of the containing forms and controls are valid. * @property {boolean} $invalid True if at least one containing control or form is invalid. * * @property {Object} $error Is an object hash, containing references to all invalid controls or * forms, where: * * - keys are validation tokens (error names), * - values are arrays of controls or forms that are invalid for given error name. * * * Built-in validation tokens: * * - `email` * - `max` * - `maxlength` * - `min` * - `minlength` * - `number` * - `pattern` * - `required` * - `url` * * @description * `FormController` keeps track of all its controls and nested forms as well as the state of them, * such as being valid/invalid or dirty/pristine. * * Each {@link ng.directive:form form} directive creates an instance * of `FormController`. * */ //asks for $scope to fool the BC controller module FormController.$inject = ['$element', '$attrs', '$scope', '$animate']; function FormController(element, attrs, $scope, $animate) { var form = this, parentForm = element.parent().controller('form') || nullFormCtrl, invalidCount = 0, // used to easily determine if we are valid errors = form.$error = {}, controls = []; // init state form.$name = attrs.name || attrs.ngForm; form.$dirty = false; form.$pristine = true; form.$valid = true; form.$invalid = false; parentForm.$addControl(form); // Setup initial state of the control element.addClass(PRISTINE_CLASS); toggleValidCss(true); // convenience method for easy toggling of classes function toggleValidCss(isValid, validationErrorKey) { validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; $animate.removeClass(element, (isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey); $animate.addClass(element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); } /** * @ngdoc method * @name form.FormController#$commitViewValue * * @description * Commit all form controls pending updates to the `$modelValue`. * * Updates may be pending by a debounced event or because the input is waiting for a some future * event defined in `ng-model-options`. This method is rarely needed as `NgModelController` * usually handles calling this in response to input events. */ form.$commitViewValue = function() { forEach(controls, function(control) { control.$commitViewValue(); }); }; /** * @ngdoc method * @name form.FormController#$addControl * * @description * Register a control with the form. * * Input elements using ngModelController do this automatically when they are linked. */ form.$addControl = function(control) { // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored // and not added to the scope. Now we throw an error. assertNotHasOwnProperty(control.$name, 'input'); controls.push(control); if (control.$name) { form[control.$name] = control; } }; /** * @ngdoc method * @name form.FormController#$removeControl * * @description * Deregister a control from the form. * * Input elements using ngModelController do this automatically when they are destroyed. */ form.$removeControl = function(control) { if (control.$name && form[control.$name] === control) { delete form[control.$name]; } forEach(errors, function(queue, validationToken) { form.$setValidity(validationToken, true, control); }); arrayRemove(controls, control); }; /** * @ngdoc method * @name form.FormController#$setValidity * * @description * Sets the validity of a form control. * * This method will also propagate to parent forms. */ form.$setValidity = function(validationToken, isValid, control) { var queue = errors[validationToken]; if (isValid) { if (queue) { arrayRemove(queue, control); if (!queue.length) { invalidCount--; if (!invalidCount) { toggleValidCss(isValid); form.$valid = true; form.$invalid = false; } errors[validationToken] = false; toggleValidCss(true, validationToken); parentForm.$setValidity(validationToken, true, form); } } } else { if (!invalidCount) { toggleValidCss(isValid); } if (queue) { if (includes(queue, control)) return; } else { errors[validationToken] = queue = []; invalidCount++; toggleValidCss(false, validationToken); parentForm.$setValidity(validationToken, false, form); } queue.push(control); form.$valid = false; form.$invalid = true; } }; /** * @ngdoc method * @name form.FormController#$setDirty * * @description * Sets the form to a dirty state. * * This method can be called to add the 'ng-dirty' class and set the form to a dirty * state (ng-dirty class). This method will also propagate to parent forms. */ form.$setDirty = function() { $animate.removeClass(element, PRISTINE_CLASS); $animate.addClass(element, DIRTY_CLASS); form.$dirty = true; form.$pristine = false; parentForm.$setDirty(); }; /** * @ngdoc method * @name form.FormController#$setPristine * * @description * Sets the form to its pristine state. * * This method can be called to remove the 'ng-dirty' class and set the form to its pristine * state (ng-pristine class). This method will also propagate to all the controls contained * in this form. * * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after * saving or resetting it. */ form.$setPristine = function () { $animate.removeClass(element, DIRTY_CLASS); $animate.addClass(element, PRISTINE_CLASS); form.$dirty = false; form.$pristine = true; forEach(controls, function(control) { control.$setPristine(); }); }; } /** * @ngdoc directive * @name ngForm * @restrict EAC * * @description * Nestable alias of {@link ng.directive:form `form`} directive. HTML * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a * sub-group of controls needs to be determined. * * Note: the purpose of `ngForm` is to group controls, * but not to be a replacement for the `
      ` tag with all of its capabilities * (e.g. posting to the server, ...). * * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into * related scope, under this name. * */ /** * @ngdoc directive * @name form * @restrict E * * @description * Directive that instantiates * {@link form.FormController FormController}. * * If the `name` attribute is specified, the form controller is published onto the current scope under * this name. * * # Alias: {@link ng.directive:ngForm `ngForm`} * * In Angular forms can be nested. This means that the outer form is valid when all of the child * forms are valid as well. However, browsers do not allow nesting of `` elements, so * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to * `` but can be nested. This allows you to have nested forms, which is very useful when * using Angular validation directives in forms that are dynamically generated using the * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name` * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an * `ngForm` directive and nest these in an outer `form` element. * * * # CSS classes * - `ng-valid` is set if the form is valid. * - `ng-invalid` is set if the form is invalid. * - `ng-pristine` is set if the form is pristine. * - `ng-dirty` is set if the form is dirty. * * Keep in mind that ngAnimate can detect each of these classes when added and removed. * * * # Submitting a form and preventing the default action * * Since the role of forms in client-side Angular applications is different than in classical * roundtrip apps, it is desirable for the browser not to translate the form submission into a full * page reload that sends the data to the server. Instead some javascript logic should be triggered * to handle the form submission in an application-specific way. * * For this reason, Angular prevents the default action (form submission to the server) unless the * `` element has an `action` attribute specified. * * You can use one of the following two ways to specify what javascript method should be called when * a form is submitted: * * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element * - {@link ng.directive:ngClick ngClick} directive on the first * button or input field of type submit (input[type=submit]) * * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} * or {@link ng.directive:ngClick ngClick} directives. * This is because of the following form submission rules in the HTML specification: * * - If a form has only one input field then hitting enter in this field triggers form submit * (`ngSubmit`) * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter * doesn't trigger submit * - if a form has one or more input fields and one or more buttons or input[type=submit] then * hitting enter in any of the input fields will trigger the click handler on the *first* button or * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) * * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` * to have access to the updated model. * * @param {string=} name Name of the form. If specified, the form controller will be published into * related scope, under this name. * * ## Animation Hooks * * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any * other validations that are performed within the form. Animations in ngForm are similar to how * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well * as JS animations. * * The following example shows a simple way to utilize CSS transitions to style a form element * that has been rendered as invalid after it has been validated: * *
       * //be sure to include ngAnimate as a module to hook into more
       * //advanced animations
       * .my-form {
       *   transition:0.5s linear all;
       *   background: white;
       * }
       * .my-form.ng-invalid {
       *   background: red;
       *   color:white;
       * }
       * 
      * * @example userType: Required!
      userType = {{userType}}
      myForm.input.$valid = {{myForm.input.$valid}}
      myForm.input.$error = {{myForm.input.$error}}
      myForm.$valid = {{myForm.$valid}}
      myForm.$error.required = {{!!myForm.$error.required}}
      it('should initialize to model', function() { var userType = element(by.binding('userType')); var valid = element(by.binding('myForm.input.$valid')); expect(userType.getText()).toContain('guest'); expect(valid.getText()).toContain('true'); }); it('should be invalid if empty', function() { var userType = element(by.binding('userType')); var valid = element(by.binding('myForm.input.$valid')); var userInput = element(by.model('userType')); userInput.clear(); userInput.sendKeys(''); expect(userType.getText()).toEqual('userType ='); expect(valid.getText()).toContain('false'); });
      * */ var formDirectiveFactory = function(isNgForm) { return ['$timeout', function($timeout) { var formDirective = { name: 'form', restrict: isNgForm ? 'EAC' : 'E', controller: FormController, compile: function() { return { pre: function(scope, formElement, attr, controller) { if (!attr.action) { // we can't use jq events because if a form is destroyed during submission the default // action is not prevented. see #1238 // // IE 9 is not affected because it doesn't fire a submit event and try to do a full // page reload if the form was destroyed by submission of the form via a click handler // on a button in the form. Looks like an IE9 specific bug. var handleFormSubmission = function(event) { scope.$apply(function() { controller.$commitViewValue(); }); event.preventDefault ? event.preventDefault() : event.returnValue = false; // IE }; addEventListenerFn(formElement[0], 'submit', handleFormSubmission); // unregister the preventDefault listener so that we don't not leak memory but in a // way that will achieve the prevention of the default action. formElement.on('$destroy', function() { $timeout(function() { removeEventListenerFn(formElement[0], 'submit', handleFormSubmission); }, 0, false); }); } var parentFormCtrl = formElement.parent().controller('form'), alias = attr.name || attr.ngForm; if (alias) { setter(scope, alias, controller, alias); } if (parentFormCtrl) { formElement.on('$destroy', function() { parentFormCtrl.$removeControl(controller); if (alias) { setter(scope, alias, undefined, alias); } extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards }); } } }; } }; return formDirective; }]; }; var formDirective = formDirectiveFactory(); var ngFormDirective = formDirectiveFactory(true); /* global -VALID_CLASS, -INVALID_CLASS, -PRISTINE_CLASS, -DIRTY_CLASS */ var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/i; var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/; var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)$/; var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/; var MONTH_REGEXP = /^(\d{4})-(\d\d)$/; var TIME_REGEXP = /^(\d\d):(\d\d)$/; var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/; var inputType = { /** * @ngdoc input * @name input[text] * * @description * Standard HTML text input with angular data binding. * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Adds `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * maxlength. * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for * patterns defined as scope expressions. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. * * @example
      Single word: Required! Single word only! text = {{text}}
      myForm.input.$valid = {{myForm.input.$valid}}
      myForm.input.$error = {{myForm.input.$error}}
      myForm.$valid = {{myForm.$valid}}
      myForm.$error.required = {{!!myForm.$error.required}}
      var text = element(by.binding('text')); var valid = element(by.binding('myForm.input.$valid')); var input = element(by.model('text')); it('should initialize to model', function() { expect(text.getText()).toContain('guest'); expect(valid.getText()).toContain('true'); }); it('should be invalid if empty', function() { input.clear(); input.sendKeys(''); expect(text.getText()).toEqual('text ='); expect(valid.getText()).toContain('false'); }); it('should be invalid if multi word', function() { input.clear(); input.sendKeys('hello world'); expect(valid.getText()).toContain('false'); });
      */ 'text': textInputType, /** * @ngdoc input * @name input[date] * * @description * Input with date validation and transformation. In browsers that do not yet support * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601 * date format (yyyy-MM-dd), for example: `2009-01-06`. The model must always be a Date object. * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a * valid ISO date string (yyyy-MM-dd). * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be * a valid ISO date string (yyyy-MM-dd). * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example
      Pick a date between in 2013: Required! Not a valid date! value = {{value | date: "yyyy-MM-dd"}}
      myForm.input.$valid = {{myForm.input.$valid}}
      myForm.input.$error = {{myForm.input.$error}}
      myForm.$valid = {{myForm.$valid}}
      myForm.$error.required = {{!!myForm.$error.required}}
      var value = element(by.binding('value | date: "yyyy-MM-dd"')); var valid = element(by.binding('myForm.input.$valid')); var input = element(by.model('value')); // currently protractor/webdriver does not support // sending keys to all known HTML5 input controls // for various browsers (see https://github.com/angular/protractor/issues/562). function setInput(val) { // set the value of the element and force validation. var scr = "var ipt = document.getElementById('exampleInput'); " + "ipt.value = '" + val + "';" + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; browser.executeScript(scr); } it('should initialize to model', function() { expect(value.getText()).toContain('2013-10-22'); expect(valid.getText()).toContain('myForm.input.$valid = true'); }); it('should be invalid if empty', function() { setInput(''); expect(value.getText()).toEqual('value ='); expect(valid.getText()).toContain('myForm.input.$valid = false'); }); it('should be invalid if over max', function() { setInput('2015-01-01'); expect(value.getText()).toContain(''); expect(valid.getText()).toContain('myForm.input.$valid = false'); });
      f */ 'date': createDateInputType('date', DATE_REGEXP, createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']), 'yyyy-MM-dd'), /** * @ngdoc input * @name input[dateTimeLocal] * * @description * Input with datetime validation and transformation. In browsers that do not yet support * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 * local datetime format (yyyy-MM-ddTHH:mm), for example: `2010-12-28T14:57`. The model must be a Date object. * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a * valid ISO datetime format (yyyy-MM-ddTHH:mm). * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be * a valid ISO datetime format (yyyy-MM-ddTHH:mm). * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example
      Pick a date between in 2013: Required! Not a valid date! value = {{value | date: "yyyy-MM-ddTHH:mm"}}
      myForm.input.$valid = {{myForm.input.$valid}}
      myForm.input.$error = {{myForm.input.$error}}
      myForm.$valid = {{myForm.$valid}}
      myForm.$error.required = {{!!myForm.$error.required}}
      var value = element(by.binding('value | date: "yyyy-MM-ddTHH:mm"')); var valid = element(by.binding('myForm.input.$valid')); var input = element(by.model('value')); // currently protractor/webdriver does not support // sending keys to all known HTML5 input controls // for various browsers (https://github.com/angular/protractor/issues/562). function setInput(val) { // set the value of the element and force validation. var scr = "var ipt = document.getElementById('exampleInput'); " + "ipt.value = '" + val + "';" + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; browser.executeScript(scr); } it('should initialize to model', function() { expect(value.getText()).toContain('2010-12-28T14:57'); expect(valid.getText()).toContain('myForm.input.$valid = true'); }); it('should be invalid if empty', function() { setInput(''); expect(value.getText()).toEqual('value ='); expect(valid.getText()).toContain('myForm.input.$valid = false'); }); it('should be invalid if over max', function() { setInput('2015-01-01T23:59'); expect(value.getText()).toContain(''); expect(valid.getText()).toContain('myForm.input.$valid = false'); });
      */ 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP, createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm']), 'yyyy-MM-ddTHH:mm'), /** * @ngdoc input * @name input[time] * * @description * Input with time validation and transformation. In browsers that do not yet support * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 * local time format (HH:mm), for example: `14:57`. Model must be a Date object. This binding will always output a * Date object to the model of January 1, 1900, or local date `new Date(0, 0, 1, HH, mm)`. * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a * valid ISO time format (HH:mm). * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be a * valid ISO time format (HH:mm). * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example
      Pick a between 8am and 5pm: Required! Not a valid date! value = {{value | date: "HH:mm"}}
      myForm.input.$valid = {{myForm.input.$valid}}
      myForm.input.$error = {{myForm.input.$error}}
      myForm.$valid = {{myForm.$valid}}
      myForm.$error.required = {{!!myForm.$error.required}}
      var value = element(by.binding('value | date: "HH:mm"')); var valid = element(by.binding('myForm.input.$valid')); var input = element(by.model('value')); // currently protractor/webdriver does not support // sending keys to all known HTML5 input controls // for various browsers (https://github.com/angular/protractor/issues/562). function setInput(val) { // set the value of the element and force validation. var scr = "var ipt = document.getElementById('exampleInput'); " + "ipt.value = '" + val + "';" + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; browser.executeScript(scr); } it('should initialize to model', function() { expect(value.getText()).toContain('14:57'); expect(valid.getText()).toContain('myForm.input.$valid = true'); }); it('should be invalid if empty', function() { setInput(''); expect(value.getText()).toEqual('value ='); expect(valid.getText()).toContain('myForm.input.$valid = false'); }); it('should be invalid if over max', function() { setInput('23:59'); expect(value.getText()).toContain(''); expect(valid.getText()).toContain('myForm.input.$valid = false'); });
      */ 'time': createDateInputType('time', TIME_REGEXP, createDateParser(TIME_REGEXP, ['HH', 'mm']), 'HH:mm'), /** * @ngdoc input * @name input[week] * * @description * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 * week format (yyyy-W##), for example: `2013-W02`. The model must always be a Date object. * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a * valid ISO week format (yyyy-W##). * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be * a valid ISO week format (yyyy-W##). * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example
      Pick a date between in 2013: Required! Not a valid date! value = {{value | date: "yyyy-Www"}}
      myForm.input.$valid = {{myForm.input.$valid}}
      myForm.input.$error = {{myForm.input.$error}}
      myForm.$valid = {{myForm.$valid}}
      myForm.$error.required = {{!!myForm.$error.required}}
      var value = element(by.binding('value | date: "yyyy-Www"')); var valid = element(by.binding('myForm.input.$valid')); var input = element(by.model('value')); // currently protractor/webdriver does not support // sending keys to all known HTML5 input controls // for various browsers (https://github.com/angular/protractor/issues/562). function setInput(val) { // set the value of the element and force validation. var scr = "var ipt = document.getElementById('exampleInput'); " + "ipt.value = '" + val + "';" + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; browser.executeScript(scr); } it('should initialize to model', function() { expect(value.getText()).toContain('2013-W01'); expect(valid.getText()).toContain('myForm.input.$valid = true'); }); it('should be invalid if empty', function() { setInput(''); expect(value.getText()).toEqual('value ='); expect(valid.getText()).toContain('myForm.input.$valid = false'); }); it('should be invalid if over max', function() { setInput('2015-W01'); expect(value.getText()).toContain(''); expect(valid.getText()).toContain('myForm.input.$valid = false'); });
      */ 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'), /** * @ngdoc input * @name input[month] * * @description * Input with month validation and transformation. In browsers that do not yet support * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 * month format (yyyy-MM), for example: `2009-01`. The model must always be a Date object. In the event the model is * not set to the first of the month, the first of that model's month is assumed. * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be * a valid ISO month format (yyyy-MM). * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must * be a valid ISO month format (yyyy-MM). * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example
      Pick a month int 2013: Required! Not a valid month! value = {{value | date: "yyyy-MM"}}
      myForm.input.$valid = {{myForm.input.$valid}}
      myForm.input.$error = {{myForm.input.$error}}
      myForm.$valid = {{myForm.$valid}}
      myForm.$error.required = {{!!myForm.$error.required}}
      var value = element(by.binding('value | date: "yyyy-MM"')); var valid = element(by.binding('myForm.input.$valid')); var input = element(by.model('value')); // currently protractor/webdriver does not support // sending keys to all known HTML5 input controls // for various browsers (https://github.com/angular/protractor/issues/562). function setInput(val) { // set the value of the element and force validation. var scr = "var ipt = document.getElementById('exampleInput'); " + "ipt.value = '" + val + "';" + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; browser.executeScript(scr); } it('should initialize to model', function() { expect(value.getText()).toContain('2013-10'); expect(valid.getText()).toContain('myForm.input.$valid = true'); }); it('should be invalid if empty', function() { setInput(''); expect(value.getText()).toEqual('value ='); expect(valid.getText()).toContain('myForm.input.$valid = false'); }); it('should be invalid if over max', function() { setInput('2015-01'); expect(value.getText()).toContain(''); expect(valid.getText()).toContain('myForm.input.$valid = false'); });
      */ 'month': createDateInputType('month', MONTH_REGEXP, createDateParser(MONTH_REGEXP, ['yyyy', 'MM']), 'yyyy-MM'), /** * @ngdoc input * @name input[number] * * @description * Text input with number validation and transformation. Sets the `number` validation * error if not a valid number. * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * maxlength. * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for * patterns defined as scope expressions. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example
      Number: Required! Not valid number! value = {{value}}
      myForm.input.$valid = {{myForm.input.$valid}}
      myForm.input.$error = {{myForm.input.$error}}
      myForm.$valid = {{myForm.$valid}}
      myForm.$error.required = {{!!myForm.$error.required}}
      var value = element(by.binding('value')); var valid = element(by.binding('myForm.input.$valid')); var input = element(by.model('value')); it('should initialize to model', function() { expect(value.getText()).toContain('12'); expect(valid.getText()).toContain('true'); }); it('should be invalid if empty', function() { input.clear(); input.sendKeys(''); expect(value.getText()).toEqual('value ='); expect(valid.getText()).toContain('false'); }); it('should be invalid if over max', function() { input.clear(); input.sendKeys('123'); expect(value.getText()).toEqual('value ='); expect(valid.getText()).toContain('false'); });
      */ 'number': numberInputType, /** * @ngdoc input * @name input[url] * * @description * Text input with URL validation. Sets the `url` validation error key if the content is not a * valid URL. * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * maxlength. * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for * patterns defined as scope expressions. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example
      URL: Required! Not valid url! text = {{text}}
      myForm.input.$valid = {{myForm.input.$valid}}
      myForm.input.$error = {{myForm.input.$error}}
      myForm.$valid = {{myForm.$valid}}
      myForm.$error.required = {{!!myForm.$error.required}}
      myForm.$error.url = {{!!myForm.$error.url}}
      var text = element(by.binding('text')); var valid = element(by.binding('myForm.input.$valid')); var input = element(by.model('text')); it('should initialize to model', function() { expect(text.getText()).toContain('http://google.com'); expect(valid.getText()).toContain('true'); }); it('should be invalid if empty', function() { input.clear(); input.sendKeys(''); expect(text.getText()).toEqual('text ='); expect(valid.getText()).toContain('false'); }); it('should be invalid if not url', function() { input.clear(); input.sendKeys('box'); expect(valid.getText()).toContain('false'); });
      */ 'url': urlInputType, /** * @ngdoc input * @name input[email] * * @description * Text input with email validation. Sets the `email` validation error key if not a valid email * address. * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * maxlength. * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for * patterns defined as scope expressions. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example
      Email: Required! Not valid email! text = {{text}}
      myForm.input.$valid = {{myForm.input.$valid}}
      myForm.input.$error = {{myForm.input.$error}}
      myForm.$valid = {{myForm.$valid}}
      myForm.$error.required = {{!!myForm.$error.required}}
      myForm.$error.email = {{!!myForm.$error.email}}
      var text = element(by.binding('text')); var valid = element(by.binding('myForm.input.$valid')); var input = element(by.model('text')); it('should initialize to model', function() { expect(text.getText()).toContain('me@example.com'); expect(valid.getText()).toContain('true'); }); it('should be invalid if empty', function() { input.clear(); input.sendKeys(''); expect(text.getText()).toEqual('text ='); expect(valid.getText()).toContain('false'); }); it('should be invalid if not email', function() { input.clear(); input.sendKeys('xxx'); expect(valid.getText()).toContain('false'); });
      */ 'email': emailInputType, /** * @ngdoc input * @name input[radio] * * @description * HTML radio button. * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string} value The value to which the expression should be set when selected. * @param {string=} name Property name of the form under which the control is published. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * @param {string} ngValue Angular expression which sets the value to which the expression should * be set when selected. * * @example
      Red
      Green
      Blue
      color = {{color | json}}
      Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`.
      it('should change state', function() { var color = element(by.binding('color')); expect(color.getText()).toContain('blue'); element.all(by.model('color')).get(0).click(); expect(color.getText()).toContain('red'); });
      */ 'radio': radioInputType, /** * @ngdoc input * @name input[checkbox] * * @description * HTML checkbox. * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} ngTrueValue The value to which the expression should be set when selected. * @param {string=} ngFalseValue The value to which the expression should be set when not selected. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example
      Value1:
      Value2:
      value1 = {{value1}}
      value2 = {{value2}}
      it('should change state', function() { var value1 = element(by.binding('value1')); var value2 = element(by.binding('value2')); expect(value1.getText()).toContain('true'); expect(value2.getText()).toContain('YES'); element(by.model('value1')).click(); element(by.model('value2')).click(); expect(value1.getText()).toContain('false'); expect(value2.getText()).toContain('NO'); });
      */ 'checkbox': checkboxInputType, 'hidden': noop, 'button': noop, 'submit': noop, 'reset': noop, 'file': noop }; // A helper function to call $setValidity and return the value / undefined, // a pattern that is repeated a lot in the input validation logic. function validate(ctrl, validatorName, validity, value){ ctrl.$setValidity(validatorName, validity); return validity ? value : undefined; } function addNativeHtml5Validators(ctrl, validatorName, element) { var validity = element.prop('validity'); if (isObject(validity)) { var validator = function(value) { // Don't overwrite previous validation, don't consider valueMissing to apply (ng-required can // perform the required validation) if (!ctrl.$error[validatorName] && (validity.badInput || validity.customError || validity.typeMismatch) && !validity.valueMissing) { ctrl.$setValidity(validatorName, false); return; } return value; }; ctrl.$parsers.push(validator); } } function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { var validity = element.prop('validity'); var placeholder = element[0].placeholder, noevent = {}; // In composition mode, users are still inputing intermediate text buffer, // hold the listener until composition is done. // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent if (!$sniffer.android) { var composing = false; element.on('compositionstart', function(data) { composing = true; }); element.on('compositionend', function() { composing = false; listener(); }); } var listener = function(ev) { if (composing) return; var value = element.val(), event = ev && ev.type; // IE (11 and under) seem to emit an 'input' event if the placeholder value changes. // We don't want to dirty the value when this happens, so we abort here. Unfortunately, // IE also sends input events for other non-input-related things, (such as focusing on a // form control), so this change is not entirely enough to solve this. if (msie && (ev || noevent).type === 'input' && element[0].placeholder !== placeholder) { placeholder = element[0].placeholder; return; } // By default we will trim the value // If the attribute ng-trim exists we will avoid trimming // e.g. if (toBoolean(attr.ngTrim || 'T')) { value = trim(value); } if (ctrl.$viewValue !== value || // If the value is still empty/falsy, and there is no `required` error, run validators // again. This enables HTML5 constraint validation errors to affect Angular validation // even when the first character entered causes an error. (validity && value === '' && !validity.valueMissing)) { if (scope.$$phase) { ctrl.$setViewValue(value, event); } else { scope.$apply(function() { ctrl.$setViewValue(value, event); }); } } }; // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the // input event on backspace, delete or cut if ($sniffer.hasEvent('input')) { element.on('input', listener); } else { var timeout; var deferListener = function(ev) { if (!timeout) { timeout = $browser.defer(function() { listener(ev); timeout = null; }); } }; element.on('keydown', function(event) { var key = event.keyCode; // ignore // command modifiers arrows if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; deferListener(event); }); // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it if ($sniffer.hasEvent('paste')) { element.on('paste cut', deferListener); } } // if user paste into input using mouse on older browser // or form autocomplete on newer browser, we need "change" event to catch it element.on('change', listener); ctrl.$render = function() { element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); }; // pattern validator var pattern = attr.ngPattern, patternValidator, match; if (pattern) { var validateRegex = function(regexp, value) { return validate(ctrl, 'pattern', ctrl.$isEmpty(value) || regexp.test(value), value); }; match = pattern.match(/^\/(.*)\/([gim]*)$/); if (match) { pattern = new RegExp(match[1], match[2]); patternValidator = function(value) { return validateRegex(pattern, value); }; } else { patternValidator = function(value) { var patternObj = scope.$eval(pattern); if (!patternObj || !patternObj.test) { throw minErr('ngPattern')('noregexp', 'Expected {0} to be a RegExp but was {1}. Element: {2}', pattern, patternObj, startingTag(element)); } return validateRegex(patternObj, value); }; } ctrl.$formatters.push(patternValidator); ctrl.$parsers.push(patternValidator); } // min length validator if (attr.ngMinlength) { var minlength = int(attr.ngMinlength); var minLengthValidator = function(value) { return validate(ctrl, 'minlength', ctrl.$isEmpty(value) || value.length >= minlength, value); }; ctrl.$parsers.push(minLengthValidator); ctrl.$formatters.push(minLengthValidator); } // max length validator if (attr.ngMaxlength) { var maxlength = int(attr.ngMaxlength); var maxLengthValidator = function(value) { return validate(ctrl, 'maxlength', ctrl.$isEmpty(value) || value.length <= maxlength, value); }; ctrl.$parsers.push(maxLengthValidator); ctrl.$formatters.push(maxLengthValidator); } } function weekParser(isoWeek) { if(isDate(isoWeek)) { return isoWeek; } if(isString(isoWeek)) { WEEK_REGEXP.lastIndex = 0; var parts = WEEK_REGEXP.exec(isoWeek); if(parts) { var year = +parts[1], week = +parts[2], firstThurs = getFirstThursdayOfYear(year), addDays = (week - 1) * 7; return new Date(year, 0, firstThurs.getDate() + addDays); } } return NaN; } function createDateParser(regexp, mapping) { return function(iso) { var parts, map; if(isDate(iso)) { return iso; } if(isString(iso)) { regexp.lastIndex = 0; parts = regexp.exec(iso); if(parts) { parts.shift(); map = { yyyy: 0, MM: 1, dd: 1, HH: 0, mm: 0 }; forEach(parts, function(part, index) { if(index < mapping.length) { map[mapping[index]] = +part; } }); return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm); } } return NaN; }; } function createDateInputType(type, regexp, parseDate, format) { return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { textInputType(scope, element, attr, ctrl, $sniffer, $browser); ctrl.$parsers.push(function(value) { if(ctrl.$isEmpty(value)) { ctrl.$setValidity(type, true); return null; } if(regexp.test(value)) { ctrl.$setValidity(type, true); return parseDate(value); } ctrl.$setValidity(type, false); return undefined; }); ctrl.$formatters.push(function(value) { if(isDate(value)) { return $filter('date')(value, format); } return ''; }); if(attr.min) { var minValidator = function(value) { var valid = ctrl.$isEmpty(value) || (parseDate(value) >= parseDate(attr.min)); ctrl.$setValidity('min', valid); return valid ? value : undefined; }; ctrl.$parsers.push(minValidator); ctrl.$formatters.push(minValidator); } if(attr.max) { var maxValidator = function(value) { var valid = ctrl.$isEmpty(value) || (parseDate(value) <= parseDate(attr.max)); ctrl.$setValidity('max', valid); return valid ? value : undefined; }; ctrl.$parsers.push(maxValidator); ctrl.$formatters.push(maxValidator); } }; } function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { textInputType(scope, element, attr, ctrl, $sniffer, $browser); ctrl.$parsers.push(function(value) { var empty = ctrl.$isEmpty(value); if (empty || NUMBER_REGEXP.test(value)) { ctrl.$setValidity('number', true); return value === '' ? null : (empty ? value : parseFloat(value)); } else { ctrl.$setValidity('number', false); return undefined; } }); addNativeHtml5Validators(ctrl, 'number', element); ctrl.$formatters.push(function(value) { return ctrl.$isEmpty(value) ? '' : '' + value; }); if (attr.min) { var minValidator = function(value) { var min = parseFloat(attr.min); return validate(ctrl, 'min', ctrl.$isEmpty(value) || value >= min, value); }; ctrl.$parsers.push(minValidator); ctrl.$formatters.push(minValidator); } if (attr.max) { var maxValidator = function(value) { var max = parseFloat(attr.max); return validate(ctrl, 'max', ctrl.$isEmpty(value) || value <= max, value); }; ctrl.$parsers.push(maxValidator); ctrl.$formatters.push(maxValidator); } ctrl.$formatters.push(function(value) { return validate(ctrl, 'number', ctrl.$isEmpty(value) || isNumber(value), value); }); } function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { textInputType(scope, element, attr, ctrl, $sniffer, $browser); var urlValidator = function(value) { return validate(ctrl, 'url', ctrl.$isEmpty(value) || URL_REGEXP.test(value), value); }; ctrl.$formatters.push(urlValidator); ctrl.$parsers.push(urlValidator); } function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { textInputType(scope, element, attr, ctrl, $sniffer, $browser); var emailValidator = function(value) { return validate(ctrl, 'email', ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value), value); }; ctrl.$formatters.push(emailValidator); ctrl.$parsers.push(emailValidator); } function radioInputType(scope, element, attr, ctrl) { // make the name unique, if not defined if (isUndefined(attr.name)) { element.attr('name', nextUid()); } var listener = function(ev) { if (element[0].checked) { scope.$apply(function() { ctrl.$setViewValue(attr.value, ev && ev.type); }); } }; element.on('click', listener); ctrl.$render = function() { var value = attr.value; element[0].checked = (value == ctrl.$viewValue); }; attr.$observe('value', ctrl.$render); } function checkboxInputType(scope, element, attr, ctrl) { var trueValue = attr.ngTrueValue, falseValue = attr.ngFalseValue; if (!isString(trueValue)) trueValue = true; if (!isString(falseValue)) falseValue = false; var listener = function(ev) { scope.$apply(function() { ctrl.$setViewValue(element[0].checked, ev && ev.type); }); }; element.on('click', listener); ctrl.$render = function() { element[0].checked = ctrl.$viewValue; }; // Override the standard `$isEmpty` because a value of `false` means empty in a checkbox. ctrl.$isEmpty = function(value) { return value !== trueValue; }; ctrl.$formatters.push(function(value) { return value === trueValue; }); ctrl.$parsers.push(function(value) { return value ? trueValue : falseValue; }); } /** * @ngdoc directive * @name textarea * @restrict E * * @description * HTML textarea element control with angular data-binding. The data-binding and validation * properties of this element are exactly the same as those of the * {@link ng.directive:input input element}. * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * maxlength. * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for * patterns defined as scope expressions. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. */ /** * @ngdoc directive * @name input * @restrict E * * @description * HTML input element control with angular data-binding. Input control follows HTML5 input types * and polyfills the HTML5 validation behavior for older browsers. * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {boolean=} ngRequired Sets `required` attribute if set to true * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * maxlength. * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for * patterns defined as scope expressions. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example
      User name: Required!
      Last name: Too short! Too long!

      user = {{user}}
      myForm.userName.$valid = {{myForm.userName.$valid}}
      myForm.userName.$error = {{myForm.userName.$error}}
      myForm.lastName.$valid = {{myForm.lastName.$valid}}
      myForm.lastName.$error = {{myForm.lastName.$error}}
      myForm.$valid = {{myForm.$valid}}
      myForm.$error.required = {{!!myForm.$error.required}}
      myForm.$error.minlength = {{!!myForm.$error.minlength}}
      myForm.$error.maxlength = {{!!myForm.$error.maxlength}}
      var user = element(by.binding('{{user}}')); var userNameValid = element(by.binding('myForm.userName.$valid')); var lastNameValid = element(by.binding('myForm.lastName.$valid')); var lastNameError = element(by.binding('myForm.lastName.$error')); var formValid = element(by.binding('myForm.$valid')); var userNameInput = element(by.model('user.name')); var userLastInput = element(by.model('user.last')); it('should initialize to model', function() { expect(user.getText()).toContain('{"name":"guest","last":"visitor"}'); expect(userNameValid.getText()).toContain('true'); expect(formValid.getText()).toContain('true'); }); it('should be invalid if empty when required', function() { userNameInput.clear(); userNameInput.sendKeys(''); expect(user.getText()).toContain('{"last":"visitor"}'); expect(userNameValid.getText()).toContain('false'); expect(formValid.getText()).toContain('false'); }); it('should be valid if empty when min length is set', function() { userLastInput.clear(); userLastInput.sendKeys(''); expect(user.getText()).toContain('{"name":"guest","last":""}'); expect(lastNameValid.getText()).toContain('true'); expect(formValid.getText()).toContain('true'); }); it('should be invalid if less than required min length', function() { userLastInput.clear(); userLastInput.sendKeys('xx'); expect(user.getText()).toContain('{"name":"guest"}'); expect(lastNameValid.getText()).toContain('false'); expect(lastNameError.getText()).toContain('minlength'); expect(formValid.getText()).toContain('false'); }); it('should be invalid if longer than max length', function() { userLastInput.clear(); userLastInput.sendKeys('some ridiculously long name'); expect(user.getText()).toContain('{"name":"guest"}'); expect(lastNameValid.getText()).toContain('false'); expect(lastNameError.getText()).toContain('maxlength'); expect(formValid.getText()).toContain('false'); });
      */ var inputDirective = ['$browser', '$sniffer', '$filter', function($browser, $sniffer, $filter) { return { restrict: 'E', require: ['?ngModel'], link: function(scope, element, attr, ctrls) { if (ctrls[0]) { (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer, $browser, $filter); } } }; }]; var VALID_CLASS = 'ng-valid', INVALID_CLASS = 'ng-invalid', PRISTINE_CLASS = 'ng-pristine', DIRTY_CLASS = 'ng-dirty'; /** * @ngdoc type * @name ngModel.NgModelController * * @property {string} $viewValue Actual string value in the view. * @property {*} $modelValue The value in the model, that the control is bound to. * @property {Array.} $parsers Array of functions to execute, as a pipeline, whenever the control reads value from the DOM. Each function is called, in turn, passing the value through to the next. The last return value is used to populate the model. Used to sanitize / convert the value as well as validation. For validation, the parsers should update the validity state using {@link ngModel.NgModelController#$setValidity $setValidity()}, and return `undefined` for invalid values. * * @property {Array.} $formatters Array of functions to execute, as a pipeline, whenever the model value changes. Each function is called, in turn, passing the value through to the next. Used to format / convert values for display in the control and validation. * ```js * function formatter(value) { * if (value) { * return value.toUpperCase(); * } * } * ngModel.$formatters.push(formatter); * ``` * * @property {Array.} $viewChangeListeners Array of functions to execute whenever the * view value has changed. It is called with no arguments, and its return value is ignored. * This can be used in place of additional $watches against the model value. * * @property {Object} $error An object hash with all errors as keys. * * @property {boolean} $pristine True if user has not interacted with the control yet. * @property {boolean} $dirty True if user has already interacted with the control. * @property {boolean} $valid True if there is no error. * @property {boolean} $invalid True if at least one error on the control. * * @description * * `NgModelController` provides API for the `ng-model` directive. The controller contains * services for data-binding, validation, CSS updates, and value formatting and parsing. It * purposefully does not contain any logic which deals with DOM rendering or listening to * DOM events. Such DOM related logic should be provided by other directives which make use of * `NgModelController` for data-binding. * * ## Custom Control Example * This example shows how to use `NgModelController` with a custom control to achieve * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) * collaborate together to achieve the desired result. * * Note that `contenteditable` is an HTML5 attribute, which tells the browser to let the element * contents be edited in place by the user. This will not work on older browsers. * * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} * module to automatically remove "bad" content like inline event listener (e.g. ``). * However, as we are using `$sce` the model can still decide to to provide unsafe content if it marks * that content using the `$sce` service. * * [contenteditable] { border: 1px solid black; background-color: white; min-height: 20px; } .ng-invalid { border: 1px solid red; } angular.module('customControl', ['ngSanitize']). directive('contenteditable', ['$sce', function($sce) { return { restrict: 'A', // only activate on element attribute require: '?ngModel', // get a hold of NgModelController link: function(scope, element, attrs, ngModel) { if(!ngModel) return; // do nothing if no ng-model // Specify how UI should be updated ngModel.$render = function() { element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); }; // Listen for change events to enable binding element.on('blur keyup change', function() { scope.$apply(read); }); read(); // initialize // Write data to the model function read() { var html = element.html(); // When we clear the content editable the browser leaves a
      behind // If strip-br attribute is provided then we strip this out if( attrs.stripBr && html == '
      ' ) { html = ''; } ngModel.$setViewValue(html); } } }; }]);
      Change me!
      Required!
      it('should data-bind and become invalid', function() { if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') { // SafariDriver can't handle contenteditable // and Firefox driver can't clear contenteditables very well return; } var contentEditable = element(by.css('[contenteditable]')); var content = 'Change me!'; expect(contentEditable.getText()).toEqual(content); contentEditable.clear(); contentEditable.sendKeys(protractor.Key.BACK_SPACE); expect(contentEditable.getText()).toEqual(''); expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); }); *
      * * */ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout) { this.$viewValue = Number.NaN; this.$modelValue = Number.NaN; this.$parsers = []; this.$formatters = []; this.$viewChangeListeners = []; this.$pristine = true; this.$dirty = false; this.$valid = true; this.$invalid = false; this.$name = $attr.name; var ngModelGet = $parse($attr.ngModel), ngModelSet = ngModelGet.assign, pendingDebounce = null, ctrl = this; if (!ngModelSet) { throw minErr('ngModel')('nonassign', "Expression '{0}' is non-assignable. Element: {1}", $attr.ngModel, startingTag($element)); } /** * @ngdoc method * @name ngModel.NgModelController#$render * * @description * Called when the view needs to be updated. It is expected that the user of the ng-model * directive will implement this method. */ this.$render = noop; /** * @ngdoc method * @name ngModel.NgModelController#$isEmpty * * @description * This is called when we need to determine if the value of the input is empty. * * For instance, the required directive does this to work out if the input has data or not. * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. * * You can override this for input directives whose concept of being empty is different to the * default. The `checkboxInputType` directive does this because in its case a value of `false` * implies empty. * * @param {*} value Reference to check. * @returns {boolean} True if `value` is empty. */ this.$isEmpty = function(value) { return isUndefined(value) || value === '' || value === null || value !== value; }; var parentForm = $element.inheritedData('$formController') || nullFormCtrl, invalidCount = 0, // used to easily determine if we are valid $error = this.$error = {}; // keep invalid keys here // Setup initial state of the control $element.addClass(PRISTINE_CLASS); toggleValidCss(true); // convenience method for easy toggling of classes function toggleValidCss(isValid, validationErrorKey) { validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; $animate.removeClass($element, (isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey); $animate.addClass($element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); } /** * @ngdoc method * @name ngModel.NgModelController#$setValidity * * @description * Change the validity state, and notifies the form when the control changes validity. (i.e. it * does not notify form if given validator is already marked as invalid). * * This method should be called by validators - i.e. the parser or formatter functions. * * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign * to `$error[validationErrorKey]=isValid` so that it is available for data-binding. * The `validationErrorKey` should be in camelCase and will get converted into dash-case * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` * class and can be bound to as `{{someForm.someControl.$error.myError}}` . * @param {boolean} isValid Whether the current state is valid (true) or invalid (false). */ this.$setValidity = function(validationErrorKey, isValid) { // Purposeful use of ! here to cast isValid to boolean in case it is undefined // jshint -W018 if ($error[validationErrorKey] === !isValid) return; // jshint +W018 if (isValid) { if ($error[validationErrorKey]) invalidCount--; if (!invalidCount) { toggleValidCss(true); ctrl.$valid = true; ctrl.$invalid = false; } } else { toggleValidCss(false); ctrl.$invalid = true; ctrl.$valid = false; invalidCount++; } $error[validationErrorKey] = !isValid; toggleValidCss(isValid, validationErrorKey); parentForm.$setValidity(validationErrorKey, isValid, ctrl); }; /** * @ngdoc method * @name ngModel.NgModelController#$setPristine * * @description * Sets the control to its pristine state. * * This method can be called to remove the 'ng-dirty' class and set the control to its pristine * state (ng-pristine class). */ this.$setPristine = function () { ctrl.$dirty = false; ctrl.$pristine = true; $animate.removeClass($element, DIRTY_CLASS); $animate.addClass($element, PRISTINE_CLASS); }; /** * @ngdoc method * @name ngModel.NgModelController#$rollbackViewValue * * @description * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`, * which may be caused by a pending debounced event or because the input is waiting for a some * future event. * * If you have an input that uses `ng-model-options` to set up debounced events or events such * as blur you can have a situation where there is a period when the `$viewValue` * is out of synch with the ngModel's `$modelValue`. * * In this case, you can run into difficulties if you try to update the ngModel's `$modelValue` * programmatically before these debounced/future events have resolved/occurred, because Angular's * dirty checking mechanism is not able to tell whether the model has actually changed or not. * * The `$rollbackViewValue()` method should be called before programmatically changing the model of an * input which may have such events pending. This is important in order to make sure that the * input field will be updated with the new model value and any pending operations are cancelled. * * * * angular.module('cancel-update-example', []) * * .controller('CancelUpdateCtrl', function($scope) { * $scope.resetWithCancel = function (e) { * if (e.keyCode == 27) { * $scope.myForm.myInput1.$rollbackViewValue(); * $scope.myValue = ''; * } * }; * $scope.resetWithoutCancel = function (e) { * if (e.keyCode == 27) { * $scope.myValue = ''; * } * }; * }); * * *
      *

      Try typing something in each input. See that the model only updates when you * blur off the input. *

      *

      Now see what happens if you start typing then press the Escape key

      * *
      *

      With $rollbackViewValue()

      *
      * myValue: "{{ myValue }}" * *

      Without $rollbackViewValue()

      *
      * myValue: "{{ myValue }}" *
      *
      *
      *
      */ this.$rollbackViewValue = function() { $timeout.cancel(pendingDebounce); ctrl.$viewValue = ctrl.$$lastCommittedViewValue; ctrl.$render(); }; /** * @ngdoc method * @name ngModel.NgModelController#$commitViewValue * * @description * Commit a pending update to the `$modelValue`. * * Updates may be pending by a debounced event or because the input is waiting for a some future * event defined in `ng-model-options`. this method is rarely needed as `NgModelController` * usually handles calling this in response to input events. */ this.$commitViewValue = function() { var value = ctrl.$viewValue; $timeout.cancel(pendingDebounce); if (ctrl.$$lastCommittedViewValue === value) { return; } ctrl.$$lastCommittedViewValue = value; // change to dirty if (ctrl.$pristine) { ctrl.$dirty = true; ctrl.$pristine = false; $animate.removeClass($element, PRISTINE_CLASS); $animate.addClass($element, DIRTY_CLASS); parentForm.$setDirty(); } forEach(ctrl.$parsers, function(fn) { value = fn(value); }); if (ctrl.$modelValue !== value) { ctrl.$modelValue = value; ngModelSet($scope, value); forEach(ctrl.$viewChangeListeners, function(listener) { try { listener(); } catch(e) { $exceptionHandler(e); } }); } }; /** * @ngdoc method * @name ngModel.NgModelController#$setViewValue * * @description * Update the view value. * * This method should be called when the view value changes, typically from within a DOM event handler. * For example {@link ng.directive:input input} and * {@link ng.directive:select select} directives call it. * * It will update the $viewValue, then pass this value through each of the functions in `$parsers`, * which includes any validators. The value that comes out of this `$parsers` pipeline, be applied to * `$modelValue` and the **expression** specified in the `ng-model` attribute. * * Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called. * * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn` * and the `default` trigger is not listed, all those actions will remain pending until one of the * `updateOn` events is triggered on the DOM element. * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions} * directive is used with a custom debounce for this particular event. * * Note that calling this function does not trigger a `$digest`. * * @param {string} value Value from the view. * @param {string} trigger Event that triggered the update. */ this.$setViewValue = function(value, trigger) { ctrl.$viewValue = value; if (!ctrl.$options || ctrl.$options.updateOnDefault) { ctrl.$$debounceViewValueCommit(trigger); } }; this.$$debounceViewValueCommit = function(trigger) { var debounceDelay = 0, options = ctrl.$options, debounce; if(options && isDefined(options.debounce)) { debounce = options.debounce; if(isNumber(debounce)) { debounceDelay = debounce; } else if(isNumber(debounce[trigger])) { debounceDelay = debounce[trigger]; } else if (isNumber(debounce['default'])) { debounceDelay = debounce['default']; } } $timeout.cancel(pendingDebounce); if (debounceDelay) { pendingDebounce = $timeout(function() { ctrl.$commitViewValue(); }, debounceDelay); } else { ctrl.$commitViewValue(); } }; // model -> value $scope.$watch(function ngModelWatch() { var value = ngModelGet($scope); // if scope model value and ngModel value are out of sync if (ctrl.$modelValue !== value) { var formatters = ctrl.$formatters, idx = formatters.length; ctrl.$modelValue = value; while(idx--) { value = formatters[idx](value); } if (ctrl.$viewValue !== value) { ctrl.$viewValue = ctrl.$$lastCommittedViewValue = value; ctrl.$render(); } } return value; }); }]; /** * @ngdoc directive * @name ngModel * * @element input * * @description * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a * property on the scope using {@link ngModel.NgModelController NgModelController}, * which is created and exposed by this directive. * * `ngModel` is responsible for: * * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` * require. * - Providing validation behavior (i.e. required, number, email, url). * - Keeping the state of the control (valid/invalid, dirty/pristine, validation errors). * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`) including animations. * - Registering the control with its parent {@link ng.directive:form form}. * * Note: `ngModel` will try to bind to the property given by evaluating the expression on the * current scope. If the property doesn't already exist on this scope, it will be created * implicitly and added to the scope. * * For best practices on using `ngModel`, see: * * - [https://github.com/angular/angular.js/wiki/Understanding-Scopes] * * For basic examples, how to use `ngModel`, see: * * - {@link ng.directive:input input} * - {@link input[text] text} * - {@link input[checkbox] checkbox} * - {@link input[radio] radio} * - {@link input[number] number} * - {@link input[email] email} * - {@link input[url] url} * - {@link input[date] date} * - {@link input[dateTimeLocal] dateTimeLocal} * - {@link input[time] time} * - {@link input[month] month} * - {@link input[week] week} * - {@link ng.directive:select select} * - {@link ng.directive:textarea textarea} * * # CSS classes * The following CSS classes are added and removed on the associated input/select/textarea element * depending on the validity of the model. * * - `ng-valid` is set if the model is valid. * - `ng-invalid` is set if the model is invalid. * - `ng-pristine` is set if the model is pristine. * - `ng-dirty` is set if the model is dirty. * * Keep in mind that ngAnimate can detect each of these classes when added and removed. * * ## Animation Hooks * * Animations within models are triggered when any of the associated CSS classes are added and removed * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`, * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. * The animations that are triggered within ngModel are similar to how they work in ngClass and * animations can be hooked into using CSS transitions, keyframes as well as JS animations. * * The following example shows a simple way to utilize CSS transitions to style an input element * that has been rendered as invalid after it has been validated: * *
       * //be sure to include ngAnimate as a module to hook into more
       * //advanced animations
       * .my-input {
       *   transition:0.5s linear all;
       *   background: white;
       * }
       * .my-input.ng-invalid {
       *   background: red;
       *   color:white;
       * }
       * 
      * * @example * Update input to see transitions when valid/invalid. Integer is a valid value.
      *
      */ var ngModelDirective = function() { return { require: ['ngModel', '^?form', '^?ngModelOptions'], controller: NgModelController, link: { pre: function(scope, element, attr, ctrls) { // Pass the ng-model-options to the ng-model controller if (ctrls[2]) { ctrls[0].$options = ctrls[2].$options; } // notify others, especially parent forms var modelCtrl = ctrls[0], formCtrl = ctrls[1] || nullFormCtrl; formCtrl.$addControl(modelCtrl); scope.$on('$destroy', function() { formCtrl.$removeControl(modelCtrl); }); }, post: function(scope, element, attr, ctrls) { var modelCtrl = ctrls[0]; if (modelCtrl.$options && modelCtrl.$options.updateOn) { element.on(modelCtrl.$options.updateOn, function(ev) { scope.$apply(function() { modelCtrl.$$debounceViewValueCommit(ev && ev.type); }); }); } } } }; }; /** * @ngdoc directive * @name ngChange * * @description * Evaluate the given expression when the user changes the input. * The expression is evaluated immediately, unlike the JavaScript onchange event * which only triggers at the end of a change (usually, when the user leaves the * form element or presses the return key). * The expression is not evaluated when the value change is coming from the model. * * Note, this directive requires `ngModel` to be present. * * @element input * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change * in input value. * * @example * * * *
      * * *
      * debug = {{confirmed}}
      * counter = {{counter}}
      *
      *
      * * var counter = element(by.binding('counter')); * var debug = element(by.binding('confirmed')); * * it('should evaluate the expression if changing from view', function() { * expect(counter.getText()).toContain('0'); * * element(by.id('ng-change-example1')).click(); * * expect(counter.getText()).toContain('1'); * expect(debug.getText()).toContain('true'); * }); * * it('should not evaluate the expression if changing from model', function() { * element(by.id('ng-change-example2')).click(); * expect(counter.getText()).toContain('0'); * expect(debug.getText()).toContain('true'); * }); * *
      */ var ngChangeDirective = valueFn({ require: 'ngModel', link: function(scope, element, attr, ctrl) { ctrl.$viewChangeListeners.push(function() { scope.$eval(attr.ngChange); }); } }); var requiredDirective = function() { return { require: '?ngModel', link: function(scope, elm, attr, ctrl) { if (!ctrl) return; attr.required = true; // force truthy in case we are on non input element var validator = function(value) { if (attr.required && ctrl.$isEmpty(value)) { ctrl.$setValidity('required', false); return; } else { ctrl.$setValidity('required', true); return value; } }; ctrl.$formatters.push(validator); ctrl.$parsers.unshift(validator); attr.$observe('required', function() { validator(ctrl.$viewValue); }); } }; }; /** * @ngdoc directive * @name ngList * * @description * Text input that converts between a delimited string and an array of strings. The delimiter * can be a fixed string (by default a comma) or a regular expression. * * @element input * @param {string=} ngList optional delimiter that should be used to split the value. If * specified in form `/something/` then the value will be converted into a regular expression. * * @example
      List: Required!
      names = {{names}}
      myForm.namesInput.$valid = {{myForm.namesInput.$valid}}
      myForm.namesInput.$error = {{myForm.namesInput.$error}}
      myForm.$valid = {{myForm.$valid}}
      myForm.$error.required = {{!!myForm.$error.required}}
      var listInput = element(by.model('names')); var names = element(by.binding('{{names}}')); var valid = element(by.binding('myForm.namesInput.$valid')); var error = element(by.css('span.error')); it('should initialize to model', function() { expect(names.getText()).toContain('["igor","misko","vojta"]'); expect(valid.getText()).toContain('true'); expect(error.getCssValue('display')).toBe('none'); }); it('should be invalid if empty', function() { listInput.clear(); listInput.sendKeys(''); expect(names.getText()).toContain(''); expect(valid.getText()).toContain('false'); expect(error.getCssValue('display')).not.toBe('none'); });
      */ var ngListDirective = function() { return { require: 'ngModel', link: function(scope, element, attr, ctrl) { var match = /\/(.*)\//.exec(attr.ngList), separator = match && new RegExp(match[1]) || attr.ngList || ','; var parse = function(viewValue) { // If the viewValue is invalid (say required but empty) it will be `undefined` if (isUndefined(viewValue)) return; var list = []; if (viewValue) { forEach(viewValue.split(separator), function(value) { if (value) list.push(trim(value)); }); } return list; }; ctrl.$parsers.push(parse); ctrl.$formatters.push(function(value) { if (isArray(value)) { return value.join(', '); } return undefined; }); // Override the standard $isEmpty because an empty array means the input is empty. ctrl.$isEmpty = function(value) { return !value || !value.length; }; } }; }; var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; /** * @ngdoc directive * @name ngValue * * @description * Binds the given expression to the value of `input[select]` or `input[radio]`, so * that when the element is selected, the `ngModel` of that element is set to the * bound value. * * `ngValue` is useful when dynamically generating lists of radio buttons using `ng-repeat`, as * shown below. * * @element input * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute * of the `input` element * * @example

      Which is your favorite?

      You chose {{my.favorite}}
      var favorite = element(by.binding('my.favorite')); it('should initialize to model', function() { expect(favorite.getText()).toContain('unicorns'); }); it('should bind the values to the inputs', function() { element.all(by.model('my.favorite')).get(0).click(); expect(favorite.getText()).toContain('pizza'); });
      */ var ngValueDirective = function() { return { priority: 100, compile: function(tpl, tplAttr) { if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { return function ngValueConstantLink(scope, elm, attr) { attr.$set('value', scope.$eval(attr.ngValue)); }; } else { return function ngValueLink(scope, elm, attr) { scope.$watch(attr.ngValue, function valueWatchAction(value) { attr.$set('value', value); }); }; } } }; }; /** * @ngdoc directive * @name ngModelOptions * * @description * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of * events that will trigger a model update and/or a debouncing delay so that the actual update only * takes place when a timer expires; this timer will be reset after another change takes place. * * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might * be different than the value in the actual model. This means that if you update the model you * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in * order to make sure it is synchronized with the model and that any debounced action is canceled. * * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`} * method is by making sure the input is placed inside a form that has a `name` attribute. This is * important because `form` controllers are published to the related scope under the name in their * `name` attribute. * * Any pending changes will take place immediately when an enclosing form is submitted via the * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` * to have access to the updated model. * * @param {Object} ngModelOptions options to apply to the current model. Valid keys are: * - `updateOn`: string specifying which event should be the input bound to. You can set several * events using an space delimited list. There is a special event called `default` that * matches the default events belonging of the control. * - `debounce`: integer value which contains the debounce model update value in milliseconds. A * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a * custom value for each event. For example: * `ngModelOptions="{ updateOn: 'default blur', debounce: {'default': 500, 'blur': 0} }"` * * @example The following example shows how to override immediate updates. Changes on the inputs within the form will update the model only when the control loses focus (blur event). If `escape` key is pressed while the input field is focused, the value is reset to the value in the current model.
      Name:
      Other data:
      user.name = 
      function Ctrl($scope) { $scope.user = { name: 'say', data: '' }; $scope.cancel = function (e) { if (e.keyCode == 27) { $scope.userForm.userName.$rollbackViewValue(); } }; } var model = element(by.binding('user.name')); var input = element(by.model('user.name')); var other = element(by.model('user.data')); it('should allow custom events', function() { input.sendKeys(' hello'); input.click(); expect(model.getText()).toEqual('say'); other.click(); expect(model.getText()).toEqual('say hello'); }); it('should $rollbackViewValue when model changes', function() { input.sendKeys(' hello'); expect(input.getAttribute('value')).toEqual('say hello'); input.sendKeys(protractor.Key.ESCAPE); expect(input.getAttribute('value')).toEqual('say'); other.click(); expect(model.getText()).toEqual('say'); });
      This one shows how to debounce model changes. Model will be updated only 1 sec after last change. If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty.
      Name:
      user.name = 
      function Ctrl($scope) { $scope.user = { name: 'say' }; }
      */ var ngModelOptionsDirective = function() { return { controller: ['$scope', '$attrs', function($scope, $attrs) { var that = this; this.$options = $scope.$eval($attrs.ngModelOptions); // Allow adding/overriding bound events if (this.$options.updateOn !== undefined) { this.$options.updateOnDefault = false; // extract "default" pseudo-event from list of events that can trigger a model update this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() { that.$options.updateOnDefault = true; return ' '; })); } else { this.$options.updateOnDefault = true; } }] }; }; /** * @ngdoc directive * @name ngBind * @restrict AC * * @description * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element * with the value of a given expression, and to update the text content when the value of that * expression changes. * * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like * `{{ expression }}` which is similar but less verbose. * * It is preferable to use `ngBind` instead of `{{ expression }}` when a template is momentarily * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an * element attribute, it makes the bindings invisible to the user while the page is loading. * * An alternative solution to this problem would be using the * {@link ng.directive:ngCloak ngCloak} directive. * * * @element ANY * @param {expression} ngBind {@link guide/expression Expression} to evaluate. * * @example * Enter a name in the Live Preview text box; the greeting below the text box changes instantly.
      Enter name:
      Hello !
      it('should check ng-bind', function() { var nameInput = element(by.model('name')); expect(element(by.binding('name')).getText()).toBe('Whirled'); nameInput.clear(); nameInput.sendKeys('world'); expect(element(by.binding('name')).getText()).toBe('world'); });
      */ var ngBindDirective = ngDirective(function(scope, element, attr) { element.addClass('ng-binding').data('$binding', attr.ngBind); scope.$watch(attr.ngBind, function ngBindWatchAction(value) { // We are purposefully using == here rather than === because we want to // catch when value is "null or undefined" // jshint -W041 element.text(value == undefined ? '' : value); }); }); /** * @ngdoc directive * @name ngBindTemplate * * @description * The `ngBindTemplate` directive specifies that the element * text content should be replaced with the interpolation of the template * in the `ngBindTemplate` attribute. * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` * expressions. This directive is needed since some HTML elements * (such as TITLE and OPTION) cannot contain SPAN elements. * * @element ANY * @param {string} ngBindTemplate template of form * {{ expression }} to eval. * * @example * Try it here: enter text in text box and watch the greeting change.
      Salutation:
      Name:
      
             
      it('should check ng-bind', function() { var salutationElem = element(by.binding('salutation')); var salutationInput = element(by.model('salutation')); var nameInput = element(by.model('name')); expect(salutationElem.getText()).toBe('Hello World!'); salutationInput.clear(); salutationInput.sendKeys('Greetings'); nameInput.clear(); nameInput.sendKeys('user'); expect(salutationElem.getText()).toBe('Greetings user!'); });
      */ var ngBindTemplateDirective = ['$interpolate', function($interpolate) { return function(scope, element, attr) { // TODO: move this to scenario runner var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); element.addClass('ng-binding').data('$binding', interpolateFn); attr.$observe('ngBindTemplate', function(value) { element.text(value); }); }; }]; /** * @ngdoc directive * @name ngBindHtml * * @description * Creates a binding that will innerHTML the result of evaluating the `expression` into the current * element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link * ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize` * is available, for example, by including {@link ngSanitize} in your module's dependencies (not in * core Angular.) You may also bypass sanitization for values you know are safe. To do so, bind to * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example * under {@link ng.$sce#Example Strict Contextual Escaping (SCE)}. * * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you * will have an exception (instead of an exploit.) * * @element ANY * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. * * @example Try it here: enter text in text box and watch the greeting change.

      angular.module('ngBindHtmlExample', ['ngSanitize']) .controller('ngBindHtmlCtrl', ['$scope', function ngBindHtmlCtrl($scope) { $scope.myHTML = 'I am an HTMLstring with links! and other stuff'; }]); it('should check ng-bind-html', function() { expect(element(by.binding('myHTML')).getText()).toBe( 'I am an HTMLstring with links! and other stuff'); });
      */ var ngBindHtmlDirective = ['$sce', '$parse', function($sce, $parse) { return function(scope, element, attr) { element.addClass('ng-binding').data('$binding', attr.ngBindHtml); var parsed = $parse(attr.ngBindHtml); function getStringValue() { var value = parsed(scope); getStringValue.$$unwatch = parsed.$$unwatch; return (value || '').toString(); } scope.$watch(getStringValue, function ngBindHtmlWatchAction(value) { element.html($sce.getTrustedHtml(parsed(scope)) || ''); }); }; }]; function classDirective(name, selector) { name = 'ngClass' + name; return ['$animate', function($animate) { return { restrict: 'AC', link: function(scope, element, attr) { var oldVal; scope.$watch(attr[name], ngClassWatchAction, true); attr.$observe('class', function(value) { ngClassWatchAction(scope.$eval(attr[name])); }); if (name !== 'ngClass') { scope.$watch('$index', function($index, old$index) { // jshint bitwise: false var mod = $index & 1; if (mod !== (old$index & 1)) { var classes = arrayClasses(scope.$eval(attr[name])); mod === selector ? addClasses(classes) : removeClasses(classes); } }); } function addClasses(classes) { var newClasses = digestClassCounts(classes, 1); attr.$addClass(newClasses); } function removeClasses(classes) { var newClasses = digestClassCounts(classes, -1); attr.$removeClass(newClasses); } function digestClassCounts (classes, count) { var classCounts = element.data('$classCounts') || {}; var classesToUpdate = []; forEach(classes, function (className) { if (count > 0 || classCounts[className]) { classCounts[className] = (classCounts[className] || 0) + count; if (classCounts[className] === +(count > 0)) { classesToUpdate.push(className); } } }); element.data('$classCounts', classCounts); return classesToUpdate.join(' '); } function updateClasses (oldClasses, newClasses) { var toAdd = arrayDifference(newClasses, oldClasses); var toRemove = arrayDifference(oldClasses, newClasses); toRemove = digestClassCounts(toRemove, -1); toAdd = digestClassCounts(toAdd, 1); if (toAdd.length === 0) { $animate.removeClass(element, toRemove); } else if (toRemove.length === 0) { $animate.addClass(element, toAdd); } else { $animate.setClass(element, toAdd, toRemove); } } function ngClassWatchAction(newVal) { if (selector === true || scope.$index % 2 === selector) { var newClasses = arrayClasses(newVal || []); if (!oldVal) { addClasses(newClasses); } else if (!equals(newVal,oldVal)) { var oldClasses = arrayClasses(oldVal); updateClasses(oldClasses, newClasses); } } oldVal = copy(newVal); } } }; function arrayDifference(tokens1, tokens2) { var values = []; outer: for(var i = 0; i < tokens1.length; i++) { var token = tokens1[i]; for(var j = 0; j < tokens2.length; j++) { if(token == tokens2[j]) continue outer; } values.push(token); } return values; } function arrayClasses (classVal) { if (isArray(classVal)) { return classVal; } else if (isString(classVal)) { return classVal.split(' '); } else if (isObject(classVal)) { var classes = [], i = 0; forEach(classVal, function(v, k) { if (v) { classes.push(k); } }); return classes; } return classVal; } }]; } /** * @ngdoc directive * @name ngClass * @restrict AC * * @description * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding * an expression that represents all classes to be added. * * The directive operates in three different ways, depending on which of three types the expression * evaluates to: * * 1. If the expression evaluates to a string, the string should be one or more space-delimited class * names. * * 2. If the expression evaluates to an array, each element of the array should be a string that is * one or more space-delimited class names. * * 3. If the expression evaluates to an object, then for each key-value pair of the * object with a truthy value the corresponding key is used as a class name. * * The directive won't add duplicate classes if a particular class was already set. * * When the expression changes, the previously added classes are removed and only then the * new classes are added. * * @animations * add - happens just before the class is applied to the element * remove - happens just before the class is removed from the element * * @element ANY * @param {expression} ngClass {@link guide/expression Expression} to eval. The result * of the evaluation can be a string representing space delimited class * names, an array, or a map of class names to boolean values. In the case of a map, the * names of the properties whose values are truthy will be added as css classes to the * element. * * @example Example that demonstrates basic bindings via ngClass directive.

      Map Syntax Example

      deleted (apply "strike" class)
      important (apply "bold" class)
      error (apply "red" class)

      Using String Syntax


      Using Array Syntax




      .strike { text-decoration: line-through; } .bold { font-weight: bold; } .red { color: red; } var ps = element.all(by.css('p')); it('should let you toggle the class', function() { expect(ps.first().getAttribute('class')).not.toMatch(/bold/); expect(ps.first().getAttribute('class')).not.toMatch(/red/); element(by.model('important')).click(); expect(ps.first().getAttribute('class')).toMatch(/bold/); element(by.model('error')).click(); expect(ps.first().getAttribute('class')).toMatch(/red/); }); it('should let you toggle string example', function() { expect(ps.get(1).getAttribute('class')).toBe(''); element(by.model('style')).clear(); element(by.model('style')).sendKeys('red'); expect(ps.get(1).getAttribute('class')).toBe('red'); }); it('array example should have 3 classes', function() { expect(ps.last().getAttribute('class')).toBe(''); element(by.model('style1')).sendKeys('bold'); element(by.model('style2')).sendKeys('strike'); element(by.model('style3')).sendKeys('red'); expect(ps.last().getAttribute('class')).toBe('bold strike red'); });
      ## Animations The example below demonstrates how to perform animations using ngClass.
      Sample Text
      .base-class { -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; } .base-class.my-class { color: red; font-size:3em; } it('should check ng-class', function() { expect(element(by.css('.base-class')).getAttribute('class')).not. toMatch(/my-class/); element(by.id('setbtn')).click(); expect(element(by.css('.base-class')).getAttribute('class')). toMatch(/my-class/); element(by.id('clearbtn')).click(); expect(element(by.css('.base-class')).getAttribute('class')).not. toMatch(/my-class/); });
      ## ngClass and pre-existing CSS3 Transitions/Animations The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure to view the step by step details of {@link ngAnimate.$animate#addclass $animate.addClass} and {@link ngAnimate.$animate#removeclass $animate.removeClass}. */ var ngClassDirective = classDirective('', true); /** * @ngdoc directive * @name ngClassOdd * @restrict AC * * @description * The `ngClassOdd` and `ngClassEven` directives work exactly as * {@link ng.directive:ngClass ngClass}, except they work in * conjunction with `ngRepeat` and take effect only on odd (even) rows. * * This directive can be applied only within the scope of an * {@link ng.directive:ngRepeat ngRepeat}. * * @element ANY * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result * of the evaluation can be a string representing space delimited class names or an array. * * @example
      1. {{name}}
      .odd { color: red; } .even { color: blue; } it('should check ng-class-odd and ng-class-even', function() { expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). toMatch(/odd/); expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). toMatch(/even/); });
      */ var ngClassOddDirective = classDirective('Odd', 0); /** * @ngdoc directive * @name ngClassEven * @restrict AC * * @description * The `ngClassOdd` and `ngClassEven` directives work exactly as * {@link ng.directive:ngClass ngClass}, except they work in * conjunction with `ngRepeat` and take effect only on odd (even) rows. * * This directive can be applied only within the scope of an * {@link ng.directive:ngRepeat ngRepeat}. * * @element ANY * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The * result of the evaluation can be a string representing space delimited class names or an array. * * @example
      1. {{name}}      
      .odd { color: red; } .even { color: blue; } it('should check ng-class-odd and ng-class-even', function() { expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). toMatch(/odd/); expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). toMatch(/even/); });
      */ var ngClassEvenDirective = classDirective('Even', 1); /** * @ngdoc directive * @name ngCloak * @restrict AC * * @description * The `ngCloak` directive is used to prevent the Angular html template from being briefly * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this * directive to avoid the undesirable flicker effect caused by the html template display. * * The directive can be applied to the `` element, but the preferred usage is to apply * multiple `ngCloak` directives to small portions of the page to permit progressive rendering * of the browser view. * * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and * `angular.min.js`. * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). * * ```css * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { * display: none !important; * } * ``` * * When this css rule is loaded by the browser, all html elements (including their children) that * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive * during the compilation of the template it deletes the `ngCloak` element attribute, making * the compiled element visible. * * For the best result, the `angular.js` script must be loaded in the head section of the html * document; alternatively, the css rule above must be included in the external stylesheet of the * application. * * Legacy browsers, like IE7, do not provide attribute selector support (added in CSS 2.1) so they * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css * class `ng-cloak` in addition to the `ngCloak` directive as shown in the example below. * * @element ANY * * @example
      {{ 'hello' }}
      {{ 'hello IE7' }}
      it('should remove the template directive and css class', function() { expect($('#template1').getAttribute('ng-cloak')). toBeNull(); expect($('#template2').getAttribute('ng-cloak')). toBeNull(); });
      * */ var ngCloakDirective = ngDirective({ compile: function(element, attr) { attr.$set('ngCloak', undefined); element.removeClass('ng-cloak'); } }); /** * @ngdoc directive * @name ngController * * @description * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular * supports the principles behind the Model-View-Controller design pattern. * * MVC components in angular: * * * Model — The Model is scope properties; scopes are attached to the DOM where scope properties * are accessed through bindings. * * View — The template (HTML with data bindings) that is rendered into the View. * * Controller — The `ngController` directive specifies a Controller class; the class contains business * logic behind the application to decorate the scope with functions and values * * Note that you can also attach controllers to the DOM by declaring it in a route definition * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller * again using `ng-controller` in the template itself. This will cause the controller to be attached * and executed twice. * * @element ANY * @scope * @param {expression} ngController Name of a globally accessible constructor function or an * {@link guide/expression expression} that on the current scope evaluates to a * constructor function. The controller instance can be published into a scope property * by specifying `as propertyName`. * * @example * Here is a simple form for editing user contact information. Adding, removing, clearing, and * greeting are methods declared on the controller (see source tab). These methods can * easily be called from the angular markup. Notice that the scope becomes the `this` for the * controller's instance. This allows for easy access to the view data from the controller. Also * notice that any changes to the data are automatically reflected in the View without the need * for a manual update. The example is shown in two different declaration styles you may use * according to preference.
      Name: [ greet ]
      Contact:
      it('should check controller as', function() { var container = element(by.id('ctrl-as-exmpl')); expect(container.findElement(by.model('settings.name')) .getAttribute('value')).toBe('John Smith'); var firstRepeat = container.findElement(by.repeater('contact in settings.contacts').row(0)); var secondRepeat = container.findElement(by.repeater('contact in settings.contacts').row(1)); expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) .toBe('408 555 1212'); expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value')) .toBe('john.smith@example.org'); firstRepeat.findElement(by.linkText('clear')).click(); expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) .toBe(''); container.findElement(by.linkText('add')).click(); expect(container.findElement(by.repeater('contact in settings.contacts').row(2)) .findElement(by.model('contact.value')) .getAttribute('value')) .toBe('yourname@example.org'); });
      Name: [ greet ]
      Contact:
      it('should check controller', function() { var container = element(by.id('ctrl-exmpl')); expect(container.findElement(by.model('name')) .getAttribute('value')).toBe('John Smith'); var firstRepeat = container.findElement(by.repeater('contact in contacts').row(0)); var secondRepeat = container.findElement(by.repeater('contact in contacts').row(1)); expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) .toBe('408 555 1212'); expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value')) .toBe('john.smith@example.org'); firstRepeat.findElement(by.linkText('clear')).click(); expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) .toBe(''); container.findElement(by.linkText('add')).click(); expect(container.findElement(by.repeater('contact in contacts').row(2)) .findElement(by.model('contact.value')) .getAttribute('value')) .toBe('yourname@example.org'); });
      */ var ngControllerDirective = [function() { return { scope: true, controller: '@', priority: 500 }; }]; /** * @ngdoc directive * @name ngCsp * * @element html * @description * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support. * * This is necessary when developing things like Google Chrome Extensions. * * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things). * For us to be compatible, we just need to implement the "getterFn" in $parse without violating * any of these restrictions. * * AngularJS uses `Function(string)` generated functions as a speed optimization. Applying the `ngCsp` * directive will cause Angular to use CSP compatibility mode. When this mode is on AngularJS will * evaluate all expressions up to 30% slower than in non-CSP mode, but no security violations will * be raised. * * CSP forbids JavaScript to inline stylesheet rules. In non CSP mode Angular automatically * includes some CSS rules (e.g. {@link ng.directive:ngCloak ngCloak}). * To make those directives work in CSP mode, include the `angular-csp.css` manually. * * In order to use this feature put the `ngCsp` directive on the root element of the application. * * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* * * @example * This example shows how to apply the `ngCsp` directive to the `html` tag. ```html ... ... ``` */ // ngCsp is not implemented as a proper directive any more, because we need it be processed while we bootstrap // the system (before $parse is instantiated), for this reason we just have a csp() fn that looks for ng-csp attribute // anywhere in the current doc /** * @ngdoc directive * @name ngClick * * @description * The ngClick directive allows you to specify custom behavior when * an element is clicked. * * @element ANY * @priority 0 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon * click. ({@link guide/expression#-event- Event object is available as `$event`}) * * @example count: {{count}} it('should check ng-click', function() { expect(element(by.binding('count')).getText()).toMatch('0'); element(by.css('button')).click(); expect(element(by.binding('count')).getText()).toMatch('1'); }); */ /* * A directive that allows creation of custom onclick handlers that are defined as angular * expressions and are compiled and executed within the current scope. * * Events that are handled via these handler are always configured not to propagate further. */ var ngEventDirectives = {}; forEach( 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), function(name) { var directiveName = directiveNormalize('ng-' + name); ngEventDirectives[directiveName] = ['$parse', function($parse) { return { compile: function($element, attr) { var fn = $parse(attr[directiveName]); return function(scope, element, attr) { element.on(lowercase(name), function(event) { scope.$apply(function() { fn(scope, {$event:event}); }); }); }; } }; }]; } ); /** * @ngdoc directive * @name ngDblclick * * @description * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. * * @element ANY * @priority 0 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon * a dblclick. (The Event object is available as `$event`) * * @example count: {{count}} */ /** * @ngdoc directive * @name ngMousedown * * @description * The ngMousedown directive allows you to specify custom behavior on mousedown event. * * @element ANY * @priority 0 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon * mousedown. ({@link guide/expression#-event- Event object is available as `$event`}) * * @example count: {{count}} */ /** * @ngdoc directive * @name ngMouseup * * @description * Specify custom behavior on mouseup event. * * @element ANY * @priority 0 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon * mouseup. ({@link guide/expression#-event- Event object is available as `$event`}) * * @example count: {{count}} */ /** * @ngdoc directive * @name ngMouseover * * @description * Specify custom behavior on mouseover event. * * @element ANY * @priority 0 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon * mouseover. ({@link guide/expression#-event- Event object is available as `$event`}) * * @example count: {{count}} */ /** * @ngdoc directive * @name ngMouseenter * * @description * Specify custom behavior on mouseenter event. * * @element ANY * @priority 0 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`}) * * @example count: {{count}} */ /** * @ngdoc directive * @name ngMouseleave * * @description * Specify custom behavior on mouseleave event. * * @element ANY * @priority 0 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`}) * * @example count: {{count}} */ /** * @ngdoc directive * @name ngMousemove * * @description * Specify custom behavior on mousemove event. * * @element ANY * @priority 0 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon * mousemove. ({@link guide/expression#-event- Event object is available as `$event`}) * * @example count: {{count}} */ /** * @ngdoc directive * @name ngKeydown * * @description * Specify custom behavior on keydown event. * * @element ANY * @priority 0 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) * * @example key down count: {{count}} */ /** * @ngdoc directive * @name ngKeyup * * @description * Specify custom behavior on keyup event. * * @element ANY * @priority 0 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) * * @example key up count: {{count}} */ /** * @ngdoc directive * @name ngKeypress * * @description * Specify custom behavior on keypress event. * * @element ANY * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon * keypress. ({@link guide/expression#-event- Event object is available as `$event`} * and can be interrogated for keyCode, altKey, etc.) * * @example key press count: {{count}} */ /** * @ngdoc directive * @name ngSubmit * * @description * Enables binding angular expressions to onsubmit events. * * Additionally it prevents the default action (which for form means sending the request to the * server and reloading the current page), but only if the form does not contain `action`, * `data-action`, or `x-action` attributes. * * @element form * @priority 0 * @param {expression} ngSubmit {@link guide/expression Expression} to eval. * ({@link guide/expression#-event- Event object is available as `$event`}) * * @example
      Enter text and hit enter:
      list={{list}}
      it('should check ng-submit', function() { expect(element(by.binding('list')).getText()).toBe('list=[]'); element(by.css('#submit')).click(); expect(element(by.binding('list')).getText()).toContain('hello'); expect(element(by.input('text')).getAttribute('value')).toBe(''); }); it('should ignore empty strings', function() { expect(element(by.binding('list')).getText()).toBe('list=[]'); element(by.css('#submit')).click(); element(by.css('#submit')).click(); expect(element(by.binding('list')).getText()).toContain('hello'); });
      */ /** * @ngdoc directive * @name ngFocus * * @description * Specify custom behavior on focus event. * * @element window, input, select, textarea, a * @priority 0 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon * focus. ({@link guide/expression#-event- Event object is available as `$event`}) * * @example * See {@link ng.directive:ngClick ngClick} */ /** * @ngdoc directive * @name ngBlur * * @description * Specify custom behavior on blur event. * * @element window, input, select, textarea, a * @priority 0 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon * blur. ({@link guide/expression#-event- Event object is available as `$event`}) * * @example * See {@link ng.directive:ngClick ngClick} */ /** * @ngdoc directive * @name ngCopy * * @description * Specify custom behavior on copy event. * * @element window, input, select, textarea, a * @priority 0 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon * copy. ({@link guide/expression#-event- Event object is available as `$event`}) * * @example copied: {{copied}} */ /** * @ngdoc directive * @name ngCut * * @description * Specify custom behavior on cut event. * * @element window, input, select, textarea, a * @priority 0 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon * cut. ({@link guide/expression#-event- Event object is available as `$event`}) * * @example cut: {{cut}} */ /** * @ngdoc directive * @name ngPaste * * @description * Specify custom behavior on paste event. * * @element window, input, select, textarea, a * @priority 0 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon * paste. ({@link guide/expression#-event- Event object is available as `$event`}) * * @example pasted: {{paste}} */ /** * @ngdoc directive * @name ngIf * @restrict A * * @description * The `ngIf` directive removes or recreates a portion of the DOM tree based on an * {expression}. If the expression assigned to `ngIf` evaluates to a false * value then the element is removed from the DOM, otherwise a clone of the * element is reinserted into the DOM. * * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the * element in the DOM rather than changing its visibility via the `display` css property. A common * case when this difference is significant is when using css selectors that rely on an element's * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classes. * * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope * is created when the element is restored. The scope created within `ngIf` inherits from * its parent scope using * [prototypal inheritance](https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance). * An important implication of this is if `ngModel` is used within `ngIf` to bind to * a javascript primitive defined in the parent scope. In this case any modifications made to the * variable within the child scope will override (hide) the value in the parent scope. * * Also, `ngIf` recreates elements using their compiled state. An example of this behavior * is if an element's class attribute is directly modified after it's compiled, using something like * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element * the added class will be lost because the original compiled state is used to regenerate the element. * * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` * and `leave` effects. * * @animations * enter - happens just after the ngIf contents change and a new DOM element is created and injected into the ngIf container * leave - happens just before the ngIf contents are removed from the DOM * * @element ANY * @scope * @priority 600 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then * the element is removed from the DOM tree. If it is truthy a copy of the compiled * element is added to the DOM tree. * * @example Click me:
      Show when checked: I'm removed when the checkbox is unchecked.
      .animate-if { background:white; border:1px solid black; padding:10px; } .animate-if.ng-enter, .animate-if.ng-leave { -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; } .animate-if.ng-enter, .animate-if.ng-leave.ng-leave-active { opacity:0; } .animate-if.ng-leave, .animate-if.ng-enter.ng-enter-active { opacity:1; }
      */ var ngIfDirective = ['$animate', function($animate) { return { transclude: 'element', priority: 600, terminal: true, restrict: 'A', $$tlb: true, link: function ($scope, $element, $attr, ctrl, $transclude) { var block, childScope, previousElements; $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { if (toBoolean(value)) { if (!childScope) { childScope = $scope.$new(); $transclude(childScope, function (clone) { clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' '); // Note: We only need the first/last node of the cloned nodes. // However, we need to keep the reference to the jqlite wrapper as it might be changed later // by a directive with templateUrl when it's template arrives. block = { clone: clone }; $animate.enter(clone, $element.parent(), $element); }); } } else { if(previousElements) { previousElements.remove(); previousElements = null; } if(childScope) { childScope.$destroy(); childScope = null; } if(block) { previousElements = getBlockElements(block.clone); $animate.leave(previousElements, function() { previousElements = null; }); block = null; } } }); } }; }]; /** * @ngdoc directive * @name ngInclude * @restrict ECA * * @description * Fetches, compiles and includes an external HTML fragment. * * By default, the template URL is restricted to the same domain and protocol as the * application document. This is done by calling {@link ng.$sce#getTrustedResourceUrl * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or * [wrap them](ng.$sce#trustAsResourceUrl) as trusted values. Refer to Angular's {@link * ng.$sce Strict Contextual Escaping}. * * In addition, the browser's * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) * policy may further restrict whether the template is successfully loaded. * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` * access on some browsers. * * @animations * enter - animation is used to bring new content into the browser. * leave - animation is used to animate existing content away. * * The enter and leave animation occur concurrently. * * @scope * @priority 400 * * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`. * @param {string=} onload Expression to evaluate when a new partial is loaded. * * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll * $anchorScroll} to scroll the viewport after the content is loaded. * * - If the attribute is not set, disable scrolling. * - If the attribute is set without value, enable scrolling. * - Otherwise enable scrolling only if the expression evaluates to truthy value. * * @example
      url of the template: {{template.url}}
      function Ctrl($scope) { $scope.templates = [ { name: 'template1.html', url: 'template1.html'}, { name: 'template2.html', url: 'template2.html'} ]; $scope.template = $scope.templates[0]; } Content of template1.html Content of template2.html .slide-animate-container { position:relative; background:white; border:1px solid black; height:40px; overflow:hidden; } .slide-animate { padding:10px; } .slide-animate.ng-enter, .slide-animate.ng-leave { -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; position:absolute; top:0; left:0; right:0; bottom:0; display:block; padding:10px; } .slide-animate.ng-enter { top:-50px; } .slide-animate.ng-enter.ng-enter-active { top:0; } .slide-animate.ng-leave { top:0; } .slide-animate.ng-leave.ng-leave-active { top:50px; } var templateSelect = element(by.model('template')); var includeElem = element(by.css('[ng-include]')); it('should load template1.html', function() { expect(includeElem.getText()).toMatch(/Content of template1.html/); }); it('should load template2.html', function() { if (browser.params.browser == 'firefox') { // Firefox can't handle using selects // See https://github.com/angular/protractor/issues/480 return; } templateSelect.click(); templateSelect.element.all(by.css('option')).get(2).click(); expect(includeElem.getText()).toMatch(/Content of template2.html/); }); it('should change to blank', function() { if (browser.params.browser == 'firefox') { // Firefox can't handle using selects return; } templateSelect.click(); templateSelect.element.all(by.css('option')).get(0).click(); expect(includeElem.isPresent()).toBe(false); });
      */ /** * @ngdoc event * @name ngInclude#$includeContentRequested * @eventType emit on the scope ngInclude was declared in * @description * Emitted every time the ngInclude content is requested. */ /** * @ngdoc event * @name ngInclude#$includeContentLoaded * @eventType emit on the current ngInclude scope * @description * Emitted every time the ngInclude content is reloaded. */ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$animate', '$sce', function($http, $templateCache, $anchorScroll, $animate, $sce) { return { restrict: 'ECA', priority: 400, terminal: true, transclude: 'element', controller: angular.noop, compile: function(element, attr) { var srcExp = attr.ngInclude || attr.src, onloadExp = attr.onload || '', autoScrollExp = attr.autoscroll; return function(scope, $element, $attr, ctrl, $transclude) { var changeCounter = 0, currentScope, previousElement, currentElement; var cleanupLastIncludeContent = function() { if(previousElement) { previousElement.remove(); previousElement = null; } if(currentScope) { currentScope.$destroy(); currentScope = null; } if(currentElement) { $animate.leave(currentElement, function() { previousElement = null; }); previousElement = currentElement; currentElement = null; } }; scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) { var afterAnimation = function() { if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { $anchorScroll(); } }; var thisChangeId = ++changeCounter; if (src) { $http.get(src, {cache: $templateCache}).success(function(response) { if (thisChangeId !== changeCounter) return; var newScope = scope.$new(); ctrl.template = response; // Note: This will also link all children of ng-include that were contained in the original // html. If that content contains controllers, ... they could pollute/change the scope. // However, using ng-include on an element with additional content does not make sense... // Note: We can't remove them in the cloneAttchFn of $transclude as that // function is called before linking the content, which would apply child // directives to non existing elements. var clone = $transclude(newScope, function(clone) { cleanupLastIncludeContent(); $animate.enter(clone, null, $element, afterAnimation); }); currentScope = newScope; currentElement = clone; currentScope.$emit('$includeContentLoaded'); scope.$eval(onloadExp); }).error(function() { if (thisChangeId === changeCounter) cleanupLastIncludeContent(); }); scope.$emit('$includeContentRequested'); } else { cleanupLastIncludeContent(); ctrl.template = null; } }); }; } }; }]; // This directive is called during the $transclude call of the first `ngInclude` directive. // It will replace and compile the content of the element with the loaded template. // We need this directive so that the element content is already filled when // the link function of another directive on the same element as ngInclude // is called. var ngIncludeFillContentDirective = ['$compile', function($compile) { return { restrict: 'ECA', priority: -400, require: 'ngInclude', link: function(scope, $element, $attr, ctrl) { $element.html(ctrl.template); $compile($element.contents())(scope); } }; }]; /** * @ngdoc directive * @name ngInit * @restrict AC * * @description * The `ngInit` directive allows you to evaluate an expression in the * current scope. * *
      * The only appropriate use of `ngInit` is for aliasing special properties of * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below. Besides this case, you * should use {@link guide/controller controllers} rather than `ngInit` * to initialize values on a scope. *
      *
      * **Note**: If you have assignment in `ngInit` along with {@link ng.$filter `$filter`}, make * sure you have parenthesis for correct precedence: *
       *   
      *
      *
      * * @priority 450 * * @element ANY * @param {expression} ngInit {@link guide/expression Expression} to eval. * * @example
      list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};
      it('should alias index positions', function() { var elements = element.all(by.css('.example-init')); expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;'); expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;'); expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;'); expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;'); });
      */ var ngInitDirective = ngDirective({ priority: 450, compile: function() { return { pre: function(scope, element, attrs) { scope.$eval(attrs.ngInit); } }; } }); /** * @ngdoc directive * @name ngNonBindable * @restrict AC * @priority 1000 * * @description * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current * DOM element. This is useful if the element contains what appears to be Angular directives and * bindings but which should be ignored by Angular. This could be the case if you have a site that * displays snippets of code, for instance. * * @element ANY * * @example * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, * but the one wrapped in `ngNonBindable` is left alone. * * @example
      Normal: {{1 + 2}}
      Ignored: {{1 + 2}}
      it('should check ng-non-bindable', function() { expect(element(by.binding('1 + 2')).getText()).toContain('3'); expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); });
      */ var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); /** * @ngdoc directive * @name ngPluralize * @restrict EA * * @description * `ngPluralize` is a directive that displays messages according to en-US localization rules. * These rules are bundled with angular.js, but can be overridden * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive * by specifying the mappings between * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) * and the strings to be displayed. * * # Plural categories and explicit number rules * There are two * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) * in Angular's default en-US locale: "one" and "other". * * While a plural category may match many numbers (for example, in en-US locale, "other" can match * any number that is not 1), an explicit number rule can only match one number. For example, the * explicit number rule for "3" matches the number 3. There are examples of plural categories * and explicit number rules throughout the rest of this documentation. * * # Configuring ngPluralize * You configure ngPluralize by providing 2 attributes: `count` and `when`. * You can also provide an optional attribute, `offset`. * * The value of the `count` attribute can be either a string or an {@link guide/expression * Angular expression}; these are evaluated on the current scope for its bound value. * * The `when` attribute specifies the mappings between plural categories and the actual * string to be displayed. The value of the attribute should be a JSON object. * * The following example shows how to configure ngPluralize: * * ```html * * *``` * * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for * other numbers, for example 12, so that instead of showing "12 people are viewing", you can * show "a dozen people are viewing". * * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted * into pluralized strings. In the previous example, Angular will replace `{}` with * `{{personCount}}`. The closed braces `{}` is a placeholder * for {{numberExpression}}. * * # Configuring ngPluralize with offset * The `offset` attribute allows further customization of pluralized text, which can result in * a better user experience. For example, instead of the message "4 people are viewing this document", * you might display "John, Kate and 2 others are viewing this document". * The offset attribute allows you to offset a number by any desired value. * Let's take a look at an example: * * ```html * * * ``` * * Notice that we are still using two plural categories(one, other), but we added * three explicit number rules 0, 1 and 2. * When one person, perhaps John, views the document, "John is viewing" will be shown. * When three people view the document, no explicit number rule is found, so * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. * In this case, plural category 'one' is matched and "John, Marry and one other person are viewing" * is shown. * * Note that when you specify offsets, you must provide explicit number rules for * numbers from 0 up to and including the offset. If you use an offset of 3, for example, * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for * plural categories "one" and "other". * * @param {string|expression} count The variable to be bound to. * @param {string} when The mapping between plural category to its corresponding strings. * @param {number=} offset Offset to deduct from the total number. * * @example
      Person 1:
      Person 2:
      Number of People:
      Without Offset:
      With Offset(2):
      it('should show correct pluralized string', function() { var withoutOffset = element.all(by.css('ng-pluralize')).get(0); var withOffset = element.all(by.css('ng-pluralize')).get(1); var countInput = element(by.model('personCount')); expect(withoutOffset.getText()).toEqual('1 person is viewing.'); expect(withOffset.getText()).toEqual('Igor is viewing.'); countInput.clear(); countInput.sendKeys('0'); expect(withoutOffset.getText()).toEqual('Nobody is viewing.'); expect(withOffset.getText()).toEqual('Nobody is viewing.'); countInput.clear(); countInput.sendKeys('2'); expect(withoutOffset.getText()).toEqual('2 people are viewing.'); expect(withOffset.getText()).toEqual('Igor and Misko are viewing.'); countInput.clear(); countInput.sendKeys('3'); expect(withoutOffset.getText()).toEqual('3 people are viewing.'); expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.'); countInput.clear(); countInput.sendKeys('4'); expect(withoutOffset.getText()).toEqual('4 people are viewing.'); expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.'); }); it('should show data-bound names', function() { var withOffset = element.all(by.css('ng-pluralize')).get(1); var personCount = element(by.model('personCount')); var person1 = element(by.model('person1')); var person2 = element(by.model('person2')); personCount.clear(); personCount.sendKeys('4'); person1.clear(); person1.sendKeys('Di'); person2.clear(); person2.sendKeys('Vojta'); expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.'); });
      */ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) { var BRACE = /{}/g; return { restrict: 'EA', link: function(scope, element, attr) { var numberExp = attr.count, whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs offset = attr.offset || 0, whens = scope.$eval(whenExp) || {}, whensExpFns = {}, startSymbol = $interpolate.startSymbol(), endSymbol = $interpolate.endSymbol(), isWhen = /^when(Minus)?(.+)$/; forEach(attr, function(expression, attributeName) { if (isWhen.test(attributeName)) { whens[lowercase(attributeName.replace('when', '').replace('Minus', '-'))] = element.attr(attr.$attr[attributeName]); } }); forEach(whens, function(expression, key) { whensExpFns[key] = $interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' + offset + endSymbol)); }); scope.$watch(function ngPluralizeWatch() { var value = parseFloat(scope.$eval(numberExp)); if (!isNaN(value)) { //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise, //check it against pluralization rules in $locale service if (!(value in whens)) value = $locale.pluralCat(value - offset); return whensExpFns[value](scope); } else { return ''; } }, function ngPluralizeWatchAction(newVal) { element.text(newVal); }); } }; }]; /** * @ngdoc directive * @name ngRepeat * * @description * The `ngRepeat` directive instantiates a template once per item from a collection. Each template * instance gets its own scope, where the given loop variable is set to the current collection item, * and `$index` is set to the item index or key. * * Special properties are exposed on the local scope of each template instance, including: * * | Variable | Type | Details | * |-----------|-----------------|-----------------------------------------------------------------------------| * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | * * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}. * This may be useful when, for instance, nesting ngRepeats. * * # Special repeat start and end points * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. * The **ng-repeat-start** directive works the same as **ng-repeat**, but will repeat all the HTML code (including the tag it's defined on) * up to and including the ending HTML tag where **ng-repeat-end** is placed. * * The example below makes use of this feature: * ```html *
      * Header {{ item }} *
      *
      * Body {{ item }} *
      *
      * Footer {{ item }} *
      * ``` * * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: * ```html *
      * Header A *
      *
      * Body A *
      *
      * Footer A *
      *
      * Header B *
      *
      * Body B *
      *
      * Footer B *
      * ``` * * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). * * @animations * **.enter** - when a new item is added to the list or when an item is revealed after a filter * * **.leave** - when an item is removed from the list or when an item is filtered out * * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered * * @element ANY * @scope * @priority 1000 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These * formats are currently supported: * * * `variable in expression` – where variable is the user defined loop variable and `expression` * is a scope expression giving the collection to enumerate. * * For example: `album in artist.albums`. * * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers, * and `expression` is the scope expression giving the collection to enumerate. * * For example: `(name, age) in {'adam':10, 'amalie':12}`. * * * `variable in expression track by tracking_expression` – You can also provide an optional tracking function * which can be used to associate the objects in the collection with the DOM elements. If no tracking function * is specified the ng-repeat associates elements by identity in the collection. It is an error to have * more than one tracking function to resolve to the same key. (This would mean that two distinct objects are * mapped to the same DOM element, which is not possible.) Filters should be applied to the expression, * before specifying a tracking expression. * * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements * will be associated by item identity in the array. * * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements * with the corresponding item in the array by identity. Moving the same object in array would move the DOM * element in the same way in the DOM. * * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this * case the object identity does not matter. Two objects are considered equivalent as long as their `id` * property is same. * * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter * to items in conjunction with a tracking expression. * * @example * This example initializes the scope to a list of names and * then uses `ngRepeat` to display every person:
      I have {{friends.length}} friends. They are:
      • [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
      .example-animate-container { background:white; border:1px solid black; list-style:none; margin:0; padding:0 10px; } .animate-repeat { line-height:40px; list-style:none; box-sizing:border-box; } .animate-repeat.ng-move, .animate-repeat.ng-enter, .animate-repeat.ng-leave { -webkit-transition:all linear 0.5s; transition:all linear 0.5s; } .animate-repeat.ng-leave.ng-leave-active, .animate-repeat.ng-move, .animate-repeat.ng-enter { opacity:0; max-height:0; } .animate-repeat.ng-leave, .animate-repeat.ng-move.ng-move-active, .animate-repeat.ng-enter.ng-enter-active { opacity:1; max-height:40px; } var friends = element.all(by.repeater('friend in friends')); it('should render initial data set', function() { expect(friends.count()).toBe(10); expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.'); expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.'); expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.'); expect(element(by.binding('friends.length')).getText()) .toMatch("I have 10 friends. They are:"); }); it('should update repeater when filter predicate changes', function() { expect(friends.count()).toBe(10); element(by.model('q')).sendKeys('ma'); expect(friends.count()).toBe(2); expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.'); expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.'); });
      */ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { var NG_REMOVED = '$$NG_REMOVED'; var ngRepeatMinErr = minErr('ngRepeat'); return { transclude: 'element', priority: 1000, terminal: true, $$tlb: true, link: function($scope, $element, $attr, ctrl, $transclude){ var expression = $attr.ngRepeat; var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/), trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn, lhs, rhs, valueIdentifier, keyIdentifier, hashFnLocals = {$id: hashKey}; if (!match) { throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", expression); } lhs = match[1]; rhs = match[2]; trackByExp = match[3]; if (trackByExp) { trackByExpGetter = $parse(trackByExp); trackByIdExpFn = function(key, value, index) { // assign key, value, and $index to the locals so that they can be used in hash functions if (keyIdentifier) hashFnLocals[keyIdentifier] = key; hashFnLocals[valueIdentifier] = value; hashFnLocals.$index = index; return trackByExpGetter($scope, hashFnLocals); }; } else { trackByIdArrayFn = function(key, value) { return hashKey(value); }; trackByIdObjFn = function(key) { return key; }; } match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/); if (!match) { throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", lhs); } valueIdentifier = match[3] || match[1]; keyIdentifier = match[2]; // Store a list of elements from previous run. This is a hash where key is the item from the // iterator, and the value is objects with following properties. // - scope: bound scope // - element: previous element. // - index: position var lastBlockMap = {}; //watch props $scope.$watchCollection(rhs, function ngRepeatAction(collection){ var index, length, previousNode = $element[0], // current position of the node nextNode, // Same as lastBlockMap but it has the current state. It will become the // lastBlockMap on the next iteration. nextBlockMap = {}, arrayLength, childScope, key, value, // key/value of iteration trackById, trackByIdFn, collectionKeys, block, // last object information {scope, element, id} nextBlockOrder = [], elementsToRemove; if (isArrayLike(collection)) { collectionKeys = collection; trackByIdFn = trackByIdExpFn || trackByIdArrayFn; } else { trackByIdFn = trackByIdExpFn || trackByIdObjFn; // if object, extract keys, sort them and use to determine order of iteration over obj props collectionKeys = []; for (key in collection) { if (collection.hasOwnProperty(key) && key.charAt(0) != '$') { collectionKeys.push(key); } } collectionKeys.sort(); } arrayLength = collectionKeys.length; // locate existing items length = nextBlockOrder.length = collectionKeys.length; for(index = 0; index < length; index++) { key = (collection === collectionKeys) ? index : collectionKeys[index]; value = collection[key]; trackById = trackByIdFn(key, value, index); assertNotHasOwnProperty(trackById, '`track by` id'); if(lastBlockMap.hasOwnProperty(trackById)) { block = lastBlockMap[trackById]; delete lastBlockMap[trackById]; nextBlockMap[trackById] = block; nextBlockOrder[index] = block; } else if (nextBlockMap.hasOwnProperty(trackById)) { // restore lastBlockMap forEach(nextBlockOrder, function(block) { if (block && block.scope) lastBlockMap[block.id] = block; }); // This is a duplicate and we need to throw an error throw ngRepeatMinErr('dupes', "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}", expression, trackById); } else { // new never before seen block nextBlockOrder[index] = { id: trackById }; nextBlockMap[trackById] = false; } } // remove existing items for (key in lastBlockMap) { // lastBlockMap is our own object so we don't need to use special hasOwnPropertyFn if (lastBlockMap.hasOwnProperty(key)) { block = lastBlockMap[key]; elementsToRemove = getBlockElements(block.clone); $animate.leave(elementsToRemove); forEach(elementsToRemove, function(element) { element[NG_REMOVED] = true; }); block.scope.$destroy(); } } // we are not using forEach for perf reasons (trying to avoid #call) for (index = 0, length = collectionKeys.length; index < length; index++) { key = (collection === collectionKeys) ? index : collectionKeys[index]; value = collection[key]; block = nextBlockOrder[index]; if (nextBlockOrder[index - 1]) previousNode = getBlockEnd(nextBlockOrder[index - 1]); if (block.scope) { // if we have already seen this object, then we need to reuse the // associated scope/element childScope = block.scope; nextNode = previousNode; do { nextNode = nextNode.nextSibling; } while(nextNode && nextNode[NG_REMOVED]); if (getBlockStart(block) != nextNode) { // existing item which got moved $animate.move(getBlockElements(block.clone), null, jqLite(previousNode)); } previousNode = getBlockEnd(block); } else { // new item which we don't know about childScope = $scope.$new(); } childScope[valueIdentifier] = value; if (keyIdentifier) childScope[keyIdentifier] = key; childScope.$index = index; childScope.$first = (index === 0); childScope.$last = (index === (arrayLength - 1)); childScope.$middle = !(childScope.$first || childScope.$last); // jshint bitwise: false childScope.$odd = !(childScope.$even = (index&1) === 0); // jshint bitwise: true if (!block.scope) { $transclude(childScope, function(clone) { clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' '); $animate.enter(clone, null, jqLite(previousNode)); previousNode = clone; block.scope = childScope; // Note: We only need the first/last node of the cloned nodes. // However, we need to keep the reference to the jqlite wrapper as it might be changed later // by a directive with templateUrl when it's template arrives. block.clone = clone; nextBlockMap[block.id] = block; }); } } lastBlockMap = nextBlockMap; }); } }; function getBlockStart(block) { return block.clone[0]; } function getBlockEnd(block) { return block.clone[block.clone.length - 1]; } }]; /** * @ngdoc directive * @name ngShow * * @description * The `ngShow` directive shows or hides the given HTML element based on the expression * provided to the ngShow attribute. The element is shown or hidden by removing or adding * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined * in AngularJS and sets the display style to none (using an !important flag). * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). * * ```html * *
      * * *
      * ``` * * When the ngShow expression evaluates to false then the ng-hide CSS class is added to the class attribute * on the element causing it to become hidden. When true, the ng-hide CSS class is removed * from the element causing the element not to appear hidden. * * ## Why is !important used? * * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector * can be easily overridden by heavier selectors. For example, something as simple * as changing the display style on a HTML list item would make hidden elements appear visible. * This also becomes a bigger issue when dealing with CSS frameworks. * * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. * * ### Overriding .ng-hide * * If you wish to change the hide behavior with ngShow/ngHide then this can be achieved by * restating the styles for the .ng-hide class in CSS: * ```css * .ng-hide { * /* Not to worry, this will override the AngularJS default... * display:block!important; * * /* this is just another form of hiding an element */ * position:absolute; * top:-9999px; * left:-9999px; * } * ``` * * Just remember to include the important flag so the CSS override will function. * *
      * **Note:** Here is a list of values that ngShow will consider as a falsy value (case insensitive):
      * "f" / "0" / "false" / "no" / "n" / "[]" *
      * * ## A note about animations with ngShow * * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression * is true and false. This system works like the animation system present with ngClass except that * you must also include the !important flag to override the display property * so that you can perform an animation when the element is hidden during the time of the animation. * * ```css * // * //a working example can be found at the bottom of this page * // * .my-element.ng-hide-add, .my-element.ng-hide-remove { * /* this is required as of 1.3x to properly * apply all styling in a show/hide animation */ * transition:0s linear all; * * /* this must be set as block so the animation is visible */ * display:block!important; * } * * .my-element.ng-hide-add-active, * .my-element.ng-hide-remove-active { * /* the transition is defined in the active class */ * transition:1s linear all; * } * * .my-element.ng-hide-add { ... } * .my-element.ng-hide-add.ng-hide-add-active { ... } * .my-element.ng-hide-remove { ... } * .my-element.ng-hide-remove.ng-hide-remove-active { ... } * ``` * * @animations * addClass: .ng-hide - happens after the ngShow expression evaluates to a truthy value and the just before contents are set to visible * removeClass: .ng-hide - happens after the ngShow expression evaluates to a non truthy value and just before the contents are set to hidden * * @element ANY * @param {expression} ngShow If the {@link guide/expression expression} is truthy * then the element is shown or hidden respectively. * * @example Click me:
      Show:
      I show up when your checkbox is checked.
      Hide:
      I hide when your checkbox is checked.
      @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css); .animate-show { line-height:20px; opacity:1; padding:10px; border:1px solid black; background:white; } .animate-show.ng-hide-add, .animate-show.ng-hide-remove { display:block!important; } .animate-show.ng-hide-add.ng-hide-add-active, .animate-show.ng-hide-remove.ng-hide-remove-active { -webkit-transition:all linear 0.5s; transition:all linear 0.5s; } .animate-show.ng-hide { line-height:0; opacity:0; padding:0 10px; } .check-element { padding:10px; border:1px solid black; background:white; } var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); it('should check ng-show / ng-hide', function() { expect(thumbsUp.isDisplayed()).toBeFalsy(); expect(thumbsDown.isDisplayed()).toBeTruthy(); element(by.model('checked')).click(); expect(thumbsUp.isDisplayed()).toBeTruthy(); expect(thumbsDown.isDisplayed()).toBeFalsy(); });
      */ var ngShowDirective = ['$animate', function($animate) { return function(scope, element, attr) { scope.$watch(attr.ngShow, function ngShowWatchAction(value){ $animate[toBoolean(value) ? 'removeClass' : 'addClass'](element, 'ng-hide'); }); }; }]; /** * @ngdoc directive * @name ngHide * * @description * The `ngHide` directive shows or hides the given HTML element based on the expression * provided to the ngHide attribute. The element is shown or hidden by removing or adding * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined * in AngularJS and sets the display style to none (using an !important flag). * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). * * ```html * *
      * * *
      * ``` * * When the ngHide expression evaluates to true then the .ng-hide CSS class is added to the class attribute * on the element causing it to become hidden. When false, the ng-hide CSS class is removed * from the element causing the element not to appear hidden. * * ## Why is !important used? * * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector * can be easily overridden by heavier selectors. For example, something as simple * as changing the display style on a HTML list item would make hidden elements appear visible. * This also becomes a bigger issue when dealing with CSS frameworks. * * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. * * ### Overriding .ng-hide * * If you wish to change the hide behavior with ngShow/ngHide then this can be achieved by * restating the styles for the .ng-hide class in CSS: * ```css * .ng-hide { * //!annotate CSS Specificity|Not to worry, this will override the AngularJS default... * display:block!important; * * //this is just another form of hiding an element * position:absolute; * top:-9999px; * left:-9999px; * } * ``` * * Just remember to include the important flag so the CSS override will function. * *
      * **Note:** Here is a list of values that ngHide will consider as a falsy value (case insensitive):
      * "f" / "0" / "false" / "no" / "n" / "[]" *
      * * ## A note about animations with ngHide * * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression * is true and false. This system works like the animation system present with ngClass, except that * you must also include the !important flag to override the display property so * that you can perform an animation when the element is hidden during the time of the animation. * * ```css * // * //a working example can be found at the bottom of this page * // * .my-element.ng-hide-add, .my-element.ng-hide-remove { * transition:0.5s linear all; * display:block!important; * } * * .my-element.ng-hide-add { ... } * .my-element.ng-hide-add.ng-hide-add-active { ... } * .my-element.ng-hide-remove { ... } * .my-element.ng-hide-remove.ng-hide-remove-active { ... } * ``` * * @animations * removeClass: .ng-hide - happens after the ngHide expression evaluates to a truthy value and just before the contents are set to hidden * addClass: .ng-hide - happens after the ngHide expression evaluates to a non truthy value and just before the contents are set to visible * * @element ANY * @param {expression} ngHide If the {@link guide/expression expression} is truthy then * the element is shown or hidden respectively. * * @example Click me:
      Show:
      I show up when your checkbox is checked.
      Hide:
      I hide when your checkbox is checked.
      @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css); .animate-hide { -webkit-transition:all linear 0.5s; transition:all linear 0.5s; line-height:20px; opacity:1; padding:10px; border:1px solid black; background:white; } .animate-hide.ng-hide-add, .animate-hide.ng-hide-remove { display:block!important; } .animate-hide.ng-hide { line-height:0; opacity:0; padding:0 10px; } .check-element { padding:10px; border:1px solid black; background:white; } var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); it('should check ng-show / ng-hide', function() { expect(thumbsUp.isDisplayed()).toBeFalsy(); expect(thumbsDown.isDisplayed()).toBeTruthy(); element(by.model('checked')).click(); expect(thumbsUp.isDisplayed()).toBeTruthy(); expect(thumbsDown.isDisplayed()).toBeFalsy(); });
      */ var ngHideDirective = ['$animate', function($animate) { return function(scope, element, attr) { scope.$watch(attr.ngHide, function ngHideWatchAction(value){ $animate[toBoolean(value) ? 'addClass' : 'removeClass'](element, 'ng-hide'); }); }; }]; /** * @ngdoc directive * @name ngStyle * @restrict AC * * @description * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. * * @element ANY * @param {expression} ngStyle * * {@link guide/expression Expression} which evals to an * object whose keys are CSS style names and values are corresponding values for those CSS * keys. * * Since some CSS style names are not valid keys for an object, they must be quoted. * See the 'background-color' style in the example below. * * @example
      Sample Text
      myStyle={{myStyle}}
      span { color: black; } var colorSpan = element(by.css('span')); it('should check ng-style', function() { expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); element(by.css('input[value=\'set color\']')).click(); expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)'); element(by.css('input[value=clear]')).click(); expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); });
      */ var ngStyleDirective = ngDirective(function(scope, element, attr) { scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { if (oldStyles && (newStyles !== oldStyles)) { forEach(oldStyles, function(val, style) { element.css(style, '');}); } if (newStyles) element.css(newStyles); }, true); }); /** * @ngdoc directive * @name ngSwitch * @restrict EA * * @description * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location * as specified in the template. * * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element * matches the value obtained from the evaluated expression. In other words, you define a container element * (where you place the directive), place an expression on the **`on="..."` attribute** * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default * attribute is displayed. * *
      * Be aware that the attribute values to match against cannot be expressions. They are interpreted * as literal string values to match against. * For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the * value of the expression `$scope.someVal`. *
      * @animations * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM * * @usage * * ``` * * ... * ... * ... * * ``` * * * @scope * @priority 800 * @param {*} ngSwitch|on expression to match against ng-switch-when. * On child elements add: * * * `ngSwitchWhen`: the case statement to match against. If match then this * case will be displayed. If the same match appears multiple times, all the * elements will be displayed. * * `ngSwitchDefault`: the default case when no other case match. If there * are multiple default cases, all of them will be displayed when no other * case match. * * * @example
      selection={{selection}}
      Settings Div
      Home Span
      default
      function Ctrl($scope) { $scope.items = ['settings', 'home', 'other']; $scope.selection = $scope.items[0]; } .animate-switch-container { position:relative; background:white; border:1px solid black; height:40px; overflow:hidden; } .animate-switch { padding:10px; } .animate-switch.ng-animate { -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; position:absolute; top:0; left:0; right:0; bottom:0; } .animate-switch.ng-leave.ng-leave-active, .animate-switch.ng-enter { top:-50px; } .animate-switch.ng-leave, .animate-switch.ng-enter.ng-enter-active { top:0; } var switchElem = element(by.css('[ng-switch]')); var select = element(by.model('selection')); it('should start in settings', function() { expect(switchElem.getText()).toMatch(/Settings Div/); }); it('should change to home', function() { select.element.all(by.css('option')).get(1).click(); expect(switchElem.getText()).toMatch(/Home Span/); }); it('should select default', function() { select.element.all(by.css('option')).get(2).click(); expect(switchElem.getText()).toMatch(/default/); });
      */ var ngSwitchDirective = ['$animate', function($animate) { return { restrict: 'EA', require: 'ngSwitch', // asks for $scope to fool the BC controller module controller: ['$scope', function ngSwitchController() { this.cases = {}; }], link: function(scope, element, attr, ngSwitchController) { var watchExpr = attr.ngSwitch || attr.on, selectedTranscludes = [], selectedElements = [], previousElements = [], selectedScopes = []; scope.$watch(watchExpr, function ngSwitchWatchAction(value) { var i, ii; for (i = 0, ii = previousElements.length; i < ii; ++i) { previousElements[i].remove(); } previousElements.length = 0; for (i = 0, ii = selectedScopes.length; i < ii; ++i) { var selected = selectedElements[i]; selectedScopes[i].$destroy(); previousElements[i] = selected; $animate.leave(selected, function() { previousElements.splice(i, 1); }); } selectedElements.length = 0; selectedScopes.length = 0; if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { scope.$eval(attr.change); forEach(selectedTranscludes, function(selectedTransclude) { var selectedScope = scope.$new(); selectedScopes.push(selectedScope); selectedTransclude.transclude(selectedScope, function(caseElement) { var anchor = selectedTransclude.element; selectedElements.push(caseElement); $animate.enter(caseElement, anchor.parent(), anchor); }); }); } }); } }; }]; var ngSwitchWhenDirective = ngDirective({ transclude: 'element', priority: 800, require: '^ngSwitch', link: function(scope, element, attrs, ctrl, $transclude) { ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []); ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element }); } }); var ngSwitchDefaultDirective = ngDirective({ transclude: 'element', priority: 800, require: '^ngSwitch', link: function(scope, element, attr, ctrl, $transclude) { ctrl.cases['?'] = (ctrl.cases['?'] || []); ctrl.cases['?'].push({ transclude: $transclude, element: element }); } }); /** * @ngdoc directive * @name ngTransclude * @restrict AC * * @description * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. * * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted. * * @element ANY * * @example


      {{text}}
      it('should have transcluded', function() { var titleElement = element(by.model('title')); titleElement.clear(); titleElement.sendKeys('TITLE'); var textElement = element(by.model('text')); textElement.clear(); textElement.sendKeys('TEXT'); expect(element(by.binding('title')).getText()).toEqual('TITLE'); expect(element(by.binding('text')).getText()).toEqual('TEXT'); });
      * */ var ngTranscludeDirective = ngDirective({ link: function($scope, $element, $attrs, controller, $transclude) { if (!$transclude) { throw minErr('ngTransclude')('orphan', 'Illegal use of ngTransclude directive in the template! ' + 'No parent directive that requires a transclusion found. ' + 'Element: {0}', startingTag($element)); } $transclude(function(clone) { $element.empty(); $element.append(clone); }); } }); /** * @ngdoc directive * @name script * @restrict E * * @description * Load the content of a ` Load inlined template
      it('should load template defined inside script tag', function() { element(by.css('#tpl-link')).click(); expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); }); */ var scriptDirective = ['$templateCache', function($templateCache) { return { restrict: 'E', terminal: true, compile: function(element, attr) { if (attr.type == 'text/ng-template') { var templateUrl = attr.id, // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent text = element[0].text; $templateCache.put(templateUrl, text); } } }; }]; var ngOptionsMinErr = minErr('ngOptions'); /** * @ngdoc directive * @name select * @restrict E * * @description * HTML `SELECT` element with angular data-binding. * * # `ngOptions` * * The `ngOptions` attribute can be used to dynamically generate a list of `` * DOM element. * * `trackexpr`: Used when working with an array of objects. The result of this expression will be * used to identify the objects in the array. The `trackexpr` will most likely refer to the * `value` variable (e.g. `value.propertyName`). * * @example

      Color (null not allowed):
      Color (null allowed):
      Color grouped by shade:
      Select bogus.

      Currently selected: {{ {selected_color:myColor} }}
      it('should check ng-options', function() { expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red'); element.all(by.select('myColor')).first().click(); element.all(by.css('select[ng-model="myColor"] option')).first().click(); expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black'); element(by.css('.nullable select[ng-model="myColor"]')).click(); element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click(); expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null'); });
      */ var ngOptionsDirective = valueFn({ terminal: true }); // jshint maxlen: false var selectDirective = ['$compile', '$parse', function($compile, $parse) { //000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888 var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/, nullModelCtrl = {$setViewValue: noop}; // jshint maxlen: 100 return { restrict: 'E', require: ['select', '?ngModel'], controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) { var self = this, optionsMap = {}, ngModelCtrl = nullModelCtrl, nullOption, unknownOption; self.databound = $attrs.ngModel; self.init = function(ngModelCtrl_, nullOption_, unknownOption_) { ngModelCtrl = ngModelCtrl_; nullOption = nullOption_; unknownOption = unknownOption_; }; self.addOption = function(value) { assertNotHasOwnProperty(value, '"option value"'); optionsMap[value] = true; if (ngModelCtrl.$viewValue == value) { $element.val(value); if (unknownOption.parent()) unknownOption.remove(); } }; self.removeOption = function(value) { if (this.hasOption(value)) { delete optionsMap[value]; if (ngModelCtrl.$viewValue == value) { this.renderUnknownOption(value); } } }; self.renderUnknownOption = function(val) { var unknownVal = '? ' + hashKey(val) + ' ?'; unknownOption.val(unknownVal); $element.prepend(unknownOption); $element.val(unknownVal); unknownOption.prop('selected', true); // needed for IE }; self.hasOption = function(value) { return optionsMap.hasOwnProperty(value); }; $scope.$on('$destroy', function() { // disable unknown option so that we don't do work when the whole select is being destroyed self.renderUnknownOption = noop; }); }], link: function(scope, element, attr, ctrls) { // if ngModel is not defined, we don't need to do anything if (!ctrls[1]) return; var selectCtrl = ctrls[0], ngModelCtrl = ctrls[1], multiple = attr.multiple, optionsExp = attr.ngOptions, nullOption = false, // if false, user will not be able to select it (used by ngOptions) emptyOption, // we can't just jqLite('