pakyow 0.10.2 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/LICENSE +1 -1
  4. data/README.md +4 -1
  5. data/bin/pakyow +2 -16
  6. data/lib/generators/pakyow/app/app_generator.rb +46 -13
  7. data/lib/generators/pakyow/app/templates/Gemfile +8 -7
  8. data/lib/generators/pakyow/app/templates/README.md +2 -2
  9. data/lib/generators/pakyow/app/templates/app/setup.rb +10 -19
  10. data/lib/generators/pakyow/app/templates/app/views/_templates/default.html +3 -1
  11. data/lib/generators/pakyow/app/templates/env +1 -0
  12. data/lib/generators/pakyow/app/templates/env.example +3 -0
  13. data/lib/generators/pakyow/app/templates/gitignore +8 -0
  14. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/fastlink.js +3 -2
  15. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/fastlink.min.js +1 -1
  16. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/ga.js +34 -0
  17. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/ga.min.js +1 -0
  18. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/modal.js +1 -1
  19. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/modal.min.js +1 -1
  20. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/mutable.js +5 -1
  21. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/mutable.min.js +1 -1
  22. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/navigator.js +10 -6
  23. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/navigator.min.js +1 -1
  24. data/lib/generators/pakyow/app/templates/public/scripts/ring/pakyow.js +175 -45
  25. data/lib/generators/pakyow/app/templates/public/scripts/ring/pakyow.min.js +1 -1
  26. data/lib/generators/pakyow/app/templates/rspec +3 -0
  27. data/lib/pakyow.rb +1 -1
  28. data/lib/pakyow/command_line_interface.rb +79 -0
  29. data/lib/pakyow/commands/console.rb +27 -0
  30. data/lib/pakyow/commands/console_methods.rb +10 -0
  31. data/lib/pakyow/commands/server.rb +38 -0
  32. data/lib/pakyow/version.rb +3 -0
  33. metadata +58 -27
  34. data/lib/commands/USAGE +0 -9
  35. data/lib/commands/USAGE-CONSOLE +0 -12
  36. data/lib/commands/USAGE-NEW +0 -11
  37. data/lib/commands/USAGE-SERVER +0 -16
  38. data/lib/commands/console.rb +0 -17
  39. data/lib/commands/server.rb +0 -27
  40. data/lib/version.rb +0 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 35ae9d36c47c1b8d13ba46589a5508496c0b09ca
4
- data.tar.gz: 3468a1d1e5b3482250e67d0190f1cf3975b76dd9
3
+ metadata.gz: 199c2331bc67c6b913a5408523fc60019770d551
4
+ data.tar.gz: b6e2c05f9d55ca795ffb44cf9ed0f7a21c22837a
5
5
  SHA512:
6
- metadata.gz: 420ab1dd7017d15645a8625e44d0a191b15c0659fb445ece91d95257590c99426e2647bca89b601881e3709c4491d215632620dd3ab173708f4bee7779511c0f
7
- data.tar.gz: 7e284f90e783752600a739324d61e082c4438695a25a26b6704ede2f6cc1482a3b835c0be1c6fb2562328c140cbf031d89e77657ceaeb98ceadd86610f891efc
6
+ metadata.gz: 1f0bfd078e041e64094ecbba209afdf4b761d760afcabe795b0b83363f112edc20ac1af5167ce63ad5bae5ab429f0dc683747d7db843a6e09fc7c4b0b60e2c46
7
+ data.tar.gz: 8191df207e1ec7e2211dbe8252c714c7b62b7a078b6cefa88c85ed493686030b1720c9d47b9f30f220faa98443afbe8fb2b88638c987f1796b0502a1b3266926
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ # 0.11.0
2
+
3
+ * Generated app updates:
4
+ * Uses dotenv for config management
5
+ * Breaks middleware into pluggable pieces
6
+ * Dynamically sets app name in config
7
+ * Creates a random session secret
8
+ * Updates Ring to 0.2.4
9
+ * Evaluates app template as erb
10
+ * Improves CLI, including new `version` command
11
+ * Moves everything into the Pakyow namespace
12
+ * Adds ASCII art to `server` command \o/
13
+
1
14
  # 0.10.2 / 2015-11-15
2
15
 
3
16
  * Updates Ring to latest in generator
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2011-2015 Bryan Powell
1
+ Copyright (c) 2011-2016 Bryan Powell
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  [gem]: https://rubygems.org/gems/pakyow
2
2
  [travis]: https://travis-ci.org/pakyow/pakyow
3
3
  [gemnasium]: https://gemnasium.com/pakyow/pakyow
4
+ [inchpages]: http://inch-ci.org/github/pakyow/pakyow
5
+ [codeclimate]: https://codeclimate.com/github/pakyow/pakyow
4
6
 
5
7
  # Pakyow Framework [![Gitter chat](https://badges.gitter.im/pakyow/chat.svg)](https://gitter.im/pakyow/chat)
6
8
 
@@ -46,6 +48,7 @@ implements this protocol on the backend for initial rendering and in
46
48
  [![Gem Version](https://badge.fury.io/rb/pakyow.svg)][gem]
47
49
  [![Build Status](https://travis-ci.org/pakyow/pakyow.svg?branch=master)][travis]
48
50
  [![Dependency Status](https://gemnasium.com/pakyow/pakyow.svg)][gemnasium]
51
+ [![Inline docs](http://inch-ci.org/github/pakyow/pakyow.svg?branch=master&style=flat)][inchpages]
49
52
 
50
53
  ---
51
54
 
@@ -61,7 +64,7 @@ implements this protocol on the backend for initial rendering and in
61
64
 
62
65
  3. Move to the new directory and start the server:
63
66
 
64
- `cd webapp; pakyow server`
67
+ `cd webapp; bundle exec pakyow server`
65
68
 
66
69
  4. You'll find your project running at [http://localhost:3000](http://localhost:3000)!
67
70
 
data/bin/pakyow CHANGED
@@ -1,18 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- PAK_PATH = File.expand_path('../../lib', __FILE__)
4
-
5
- case ARGV.first
6
- when 'new'
7
- ARGV.shift
8
- require File.join(PAK_PATH, 'generators/pakyow/app/app_generator')
9
- Pakyow::Generators::AppGenerator.start
10
- when 'server', 's'
11
- ARGV.shift
12
- require File.join(PAK_PATH, 'commands/server')
13
- when 'console', 'c'
14
- ARGV.shift
15
- require File.join(PAK_PATH, 'commands/console')
16
- when '--help', '-h', nil
17
- puts File.open(File.join(PAK_PATH, 'commands/USAGE')).read
18
- end
3
+ require "pakyow/command_line_interface"
4
+ Pakyow::CommandLineInterface.start
@@ -1,20 +1,25 @@
1
+ require 'erb'
1
2
  require 'fileutils'
3
+ require 'securerandom'
4
+ require 'pakyow/version.rb'
2
5
 
3
6
  module Pakyow
4
7
  module Generators
5
8
  class AppGenerator
6
9
  class << self
7
- def start
8
- case ARGV.first
9
- when '--help', '-h', nil
10
- puts File.open(File.join(PAK_PATH, 'commands/USAGE-NEW')).read
11
- else
12
- generator = self.new(ARGV.first)
13
- generator.build
14
- end
10
+ def start(destination)
11
+ generator = self.new(destination)
12
+ generator.build
15
13
  end
16
14
  end
17
15
 
16
+ FILENAME_TRANSLATIONS = {
17
+ 'rspec' => '.rspec',
18
+ 'gitignore' => '.gitignore',
19
+ 'env' => '.env',
20
+ 'env.example' => '.env.example'
21
+ }
22
+
18
23
  def initialize(dest)
19
24
  @src = "#{File.expand_path('../', __FILE__)}/templates/."
20
25
  @dest = dest
@@ -38,23 +43,51 @@ module Pakyow
38
43
  end
39
44
 
40
45
  exec
41
- puts "Done! Run `cd #{@dest}; pakyow server` to get started!"
46
+ puts "Done! Run `cd #{@dest}; bundle exec pakyow server` to get started!"
42
47
  end
43
48
 
44
49
  protected
45
50
 
46
- # copies src files to dest
47
51
  def copy
48
- FileUtils.cp_r(@src, @dest)
52
+ FileUtils.mkdir(@dest) unless File.exists?(@dest)
53
+
54
+ Dir.glob(File.join(@src, '**', '*')).each do |path|
55
+ relative_path = path[@src.length..-1]
56
+ generated_path = File.join(@dest, File.dirname(relative_path), translated_filename(File.basename(relative_path)))
57
+
58
+ if File.directory?(path)
59
+ FileUtils.mkdir(generated_path)
60
+ next
61
+ end
62
+
63
+ erb = ERB.new(File.read(path))
64
+ File.open(generated_path, 'w') { |f| f.write(erb.result(binding)) }
65
+ end
49
66
  end
50
67
 
51
- # performs and other setup (e.g. bundle install)
52
68
  def exec
53
69
  FileUtils.cd(@dest) do
54
70
  puts "Running `bundle install` in #{Dir.pwd}"
55
- system("bundle install")
71
+ system("bundle install --binstubs")
56
72
  end
57
73
  end
74
+
75
+ def generating_locally?
76
+ local_pakyow = Gem::Specification.sort_by{ |g| [g.name.downcase, g.version] }.group_by{ |g| g.name }.detect{|k,v| k == 'pakyow'}
77
+ !local_pakyow || local_pakyow.last.last.version < Gem::Version.new(Pakyow::VERSION)
78
+ end
79
+
80
+ def translated_filename(filename)
81
+ FILENAME_TRANSLATIONS.fetch(filename, filename)
82
+ end
83
+
84
+ def generate_session_secret
85
+ SecureRandom.hex(64)
86
+ end
87
+
88
+ def app_name
89
+ File.basename(@dest)
90
+ end
58
91
  end
59
92
  end
60
93
  end
@@ -1,15 +1,16 @@
1
1
  source 'https://rubygems.org'
2
-
3
- # live on the edge and uncomment this line
4
- # gem 'pakyow', github: 'pakyow/pakyow'
5
-
6
- # use the latest released version
7
- gem 'pakyow', '~> 0.10'
8
-
2
+ <% if generating_locally? %>
3
+ gem 'pakyow', github: 'pakyow/pakyow'
4
+ <% else %>
5
+ gem 'pakyow', '~> <%= Pakyow::VERSION %>'
6
+ <% end %>
9
7
  # app server
10
8
  gem 'puma', platforms: :ruby
11
9
  gem 'thin', platforms: :mswin
12
10
 
11
+ # use dotenv to load environment variables
12
+ gem 'dotenv'
13
+
13
14
  group :test do
14
15
  gem 'rspec'
15
16
  end
@@ -4,13 +4,13 @@ This is a Pakyow v0.10 project.
4
4
 
5
5
  Start the server:
6
6
 
7
- `pakyow server`
7
+ `bundle exec pakyow server`
8
8
 
9
9
  You'll find your app running at [http://localhost:3000](http://localhost:3000)!
10
10
 
11
11
  Need to interact with your code? Fire up a console:
12
12
 
13
- `pakyow console`
13
+ `bundle exec pakyow console`
14
14
 
15
15
  # Next Steps
16
16
 
@@ -2,32 +2,23 @@ require 'bundler/setup'
2
2
  require 'pakyow'
3
3
 
4
4
  Pakyow::App.define do
5
- configure :global do
5
+ configure do
6
6
  Bundler.require :default, Pakyow::Config.env
7
7
 
8
- # put global config here and they'll be available across environments
9
- app.name = 'Pakyow'
10
- end
11
-
12
- configure :development do
13
- # put development config here
14
- end
8
+ if defined?(Dotenv)
9
+ env_path = ".env.#{Pakyow::Config.env}"
10
+ Dotenv.load env_path if File.exist?(env_path)
11
+ Dotenv.load
12
+ end
15
13
 
16
- configure :prototype do
17
- # an environment for running the front-end prototype with no backend
18
- app.ignore_routes = true
14
+ app.name = '<%= app_name %>'
19
15
  end
20
16
 
21
- configure :staging do
22
- # put your staging config here
17
+ configure :development do
18
+ # development config goes here
23
19
  end
24
20
 
25
21
  configure :production do
26
- # put your production config here
27
- end
28
-
29
- middleware do |builder|
30
- # TODO: you will most definitely want to change this secret
31
- builder.use Rack::Session::Cookie, key: "#{Pakyow::Config.app.name}.session", secret: 'sekret'
22
+ # production config goes here
32
23
  end
33
24
  end
@@ -2,7 +2,7 @@
2
2
  <html>
3
3
  <head>
4
4
  <title>
5
- Pakyow
5
+ <%= app_name %>
6
6
  </title>
7
7
 
8
8
  <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0" name="viewport">
@@ -13,6 +13,8 @@
13
13
  <link rel="stylesheet" type="text/css" href="/styles/pakyow-css/theme.css">
14
14
 
15
15
  <script src="/scripts/ring/pakyow.js"></script>
16
+
17
+ <meta charset="UTF-8">
16
18
  </head>
17
19
 
18
20
  <body>
@@ -0,0 +1 @@
1
+ SESSION_SECRET=<%= generate_session_secret %>
@@ -0,0 +1,3 @@
1
+ # Please place the following environment variables in your local `.env` file.
2
+
3
+ SESSION_SECRET=sekret
@@ -0,0 +1,8 @@
1
+ # ignore bundler config
2
+ .bundle
3
+
4
+ # ignore environment files
5
+ .env*
6
+
7
+ # ignore binstubs
8
+ bin
@@ -1,8 +1,9 @@
1
1
  pw.component.register('fastlink', function (view, config) {
2
- var that = this;
3
-
4
2
  if (window.history) {
5
3
  view.node.addEventListener('click', function (evt) {
4
+ // don't break open in new tab!
5
+ if (evt.metaKey || evt.ctrlKey) return;
6
+
6
7
  evt.preventDefault();
7
8
  window.history.pushState({ uri: this.href }, this.href, this.href);
8
9
  return false;
@@ -1 +1 @@
1
- pw.component.register("fastlink",function(t,e){window.history&&t.node.addEventListener("click",function(t){return t.preventDefault(),window.history.pushState({uri:this.href},this.href,this.href),!1})});
1
+ pw.component.register("fastlink",function(t,e){window.history&&t.node.addEventListener("click",function(t){return t.metaKey||t.ctrlKey?void 0:(t.preventDefault(),window.history.pushState({uri:this.href},this.href,this.href),!1)})});
@@ -0,0 +1,34 @@
1
+ /*
2
+ Ring.js - Google Analytics Component
3
+
4
+ Sets up Google Analytics and tracks the immediate pageview along with any
5
+ pageviews that occur over a WebSocket connection with Navigator. To use,
6
+ attach the component to the <body> tag and configure the trackingId:
7
+
8
+ <body data-ui="ga" data-config="trackingId: yourTrackingIdHere">
9
+
10
+ It will automatically ignore pageviews that occur locally.
11
+ */
12
+
13
+ pw.component.register('ga', function (view, config) {
14
+ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
15
+ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
16
+ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
17
+ })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
18
+ ga('create', config.trackingId, 'auto');
19
+
20
+ this.track = function (uri) {
21
+ if (document.domain.indexOf('local') != -1) {
22
+ return;
23
+ }
24
+
25
+ ga('send', 'pageview', uri);
26
+ };
27
+
28
+ this.listen('navigator:change', function (payload) {
29
+ this.track(payload.uri);
30
+ });
31
+
32
+ // track the current pageview
33
+ this.track(location.pathname);
34
+ });
@@ -0,0 +1 @@
1
+ pw.component.register("ga",function(t,n){!function(t,n,e,a,c,i,o){t.GoogleAnalyticsObject=c,t[c]=t[c]||function(){(t[c].q=t[c].q||[]).push(arguments)},t[c].l=1*new Date,i=n.createElement(e),o=n.getElementsByTagName(e)[0],i.async=1,i.src=a,o.parentNode.insertBefore(i,o)}(window,document,"script","//www.google-analytics.com/analytics.js","ga"),ga("create",n.trackingId,"auto"),this.track=function(t){-1==document.domain.indexOf("local")&&ga("send","pageview",t)},this.listen("navigator:change",function(t){this.track(t.uri)}),this.track(location.pathname)});
@@ -60,7 +60,7 @@ pw.component.register('modal', function (view, config, name, id) {
60
60
 
61
61
  view.node.addEventListener('click', function (evt) {
62
62
  evt.preventDefault();
63
- self.load(this.href);
63
+ self.load(config.href || this.href);
64
64
  return false;
65
65
  });
66
66
 
@@ -1 +1 @@
1
- pw.component.register("modal",function(t,e,n,o){var a,i,d,l=this,r="modal:"+o;(d=document.querySelector('*[data-template="ui-modal-blinder"]'))&&(d=d.cloneNode(!0)),this.listen(r+":navigator:enter",function(t){a||(d?(a=d.cloneNode(!0),i=a.querySelector('*[data-template="ui-modal-content"]'),document.body.appendChild(a),a.removeAttribute("data-template"),i.removeAttribute("data-template")):(a=document.createElement("DIV"),a.classList.add("ui-modal-blinder"),i=document.createElement("DIV"),i.classList.add("ui-modal"),a.appendChild(i),document.body.appendChild(a)),a.addEventListener("click",function(t){if(t.target===a){t.preventDefault(),l.close();var e=window.location.pathname,n={uri:e};window.history.pushState(n,e,e)}})),i.innerHTML=t.body,pw.component.findAndInit(a),a.classList.add("ui-appear")}),this.listen(r+":navigator:exit",function(){l.close()}),this.listen(r+":navigator:boot",function(t){l.load(t)}),t.node.addEventListener("click",function(t){return t.preventDefault(),l.load(this.href),!1}),this.load=function(t){if(!window.socket)return void(document.location=t);var n={uri:t,context:"modal:"+o};e.container&&(n.container=e.container),e.partial&&(n.partial=e.partial),window.history.pushState(n,t,t)},this.close=function(){a&&i&&(pw.node.remove(a),a=null,i=null)}});
1
+ pw.component.register("modal",function(t,e,n,o){var a,i,d,l=this,r="modal:"+o;(d=document.querySelector('*[data-template="ui-modal-blinder"]'))&&(d=d.cloneNode(!0)),this.listen(r+":navigator:enter",function(t){a||(d?(a=d.cloneNode(!0),i=a.querySelector('*[data-template="ui-modal-content"]'),document.body.appendChild(a),a.removeAttribute("data-template"),i.removeAttribute("data-template")):(a=document.createElement("DIV"),a.classList.add("ui-modal-blinder"),i=document.createElement("DIV"),i.classList.add("ui-modal"),a.appendChild(i),document.body.appendChild(a)),a.addEventListener("click",function(t){if(t.target===a){t.preventDefault(),l.close();var e=window.location.pathname,n={uri:e};window.history.pushState(n,e,e)}})),i.innerHTML=t.body,pw.component.findAndInit(a),a.classList.add("ui-appear")}),this.listen(r+":navigator:exit",function(){l.close()}),this.listen(r+":navigator:boot",function(t){l.load(t)}),t.node.addEventListener("click",function(t){return t.preventDefault(),l.load(e.href||this.href),!1}),this.load=function(t){if(!window.socket)return void(document.location=t);var n={uri:t,context:"modal:"+o};e.container&&(n.container=e.container),e.partial&&(n.partial=e.partial),window.history.pushState(n,t,t)},this.close=function(){a&&i&&(pw.node.remove(a),a=null,i=null)}});
@@ -54,13 +54,17 @@ pw.component.register('mutable', function (view, config) {
54
54
  }
55
55
  } else if (res.status === 400) {
56
56
  // bad request
57
+ pw.component.broadcast('response:received', { response: res });
57
58
  return;
58
59
  } else {
59
60
  self.state.rollback();
60
61
  }
61
62
 
62
63
  pw.component.broadcast('response:received', { response: res });
63
- self.revert();
64
+
65
+ if (config.revert !== 'false') {
66
+ self.revert();
67
+ }
64
68
  });
65
69
  }
66
70
  });
@@ -1 +1 @@
1
- pw.component.register("mutable",function(e,t){this.mutation=function(t){if(!window.socket)return void e.node.submit();var o=pw.util.dup(t);delete o.__nested,delete o.scope,delete o.id;var n={action:"call-route"};if("FORM"===e.node.tagName){if(e.node.querySelector('input[type="file"]'))return void e.node.submit();var i,r=e.node.querySelector('input[name="_method"]');i=r?r.value:e.node.getAttribute("method"),n.method=i,n.uri=e.node.getAttribute("action"),n.input=pw.node.serialize(e.node)}else{var a={};a[t.scope]=o,n.input=a}var d=this;window.socket.send(n,function(e){if(302===e.status){var t=e.headers.Location;t!=window.location.pathname||window.context&&"default"===window.context.name?history.pushState({uri:t},t,t):history.pushState({uri:t},t,t)}else{if(400===e.status)return;d.state.rollback()}pw.component.broadcast("response:received",{response:e}),d.revert()})}});
1
+ pw.component.register("mutable",function(e,t){this.mutation=function(o){if(!window.socket)return void e.node.submit();var n=pw.util.dup(o);delete n.__nested,delete n.scope,delete n.id;var i={action:"call-route"};if("FORM"===e.node.tagName){if(e.node.querySelector('input[type="file"]'))return void e.node.submit();var r,s=e.node.querySelector('input[name="_method"]');r=s?s.value:e.node.getAttribute("method"),i.method=r,i.uri=e.node.getAttribute("action"),i.input=pw.node.serialize(e.node)}else{var a={};a[o.scope]=n,i.input=a}var d=this;window.socket.send(i,function(e){if(302===e.status){var o=e.headers.Location;o!=window.location.pathname||window.context&&"default"===window.context.name?history.pushState({uri:o},o,o):history.pushState({uri:o},o,o)}else{if(400===e.status)return void pw.component.broadcast("response:received",{response:e});d.state.rollback()}pw.component.broadcast("response:received",{response:e}),"false"!==t.revert&&d.revert()})}});
@@ -80,6 +80,9 @@ function handleState(state, direction) {
80
80
  return;
81
81
  }
82
82
 
83
+ uri = uri.replace(document.location.origin, '');
84
+ pw.component.broadcast('navigator:change', { uri: uri });
85
+
83
86
  if (state.context) {
84
87
  state.r_uri = document.location.pathname + '#:' + state.context + '/' + uri;
85
88
 
@@ -135,16 +138,17 @@ function handleState(state, direction) {
135
138
  var body = payload.body[0];
136
139
 
137
140
  if (body.match(/<title>/)) {
138
- document.title = body.split(/<title>/)[1].split('</title>')[0];
141
+ var title = body.split(/<title>/)[1].split('</title>')[0];
142
+ document.querySelector('title').innerHTML = title;
139
143
  }
140
144
 
141
- if (body.match(/<body [^>]*>/)) {
142
- document.body.innerHTML = body.split(/<body [^>]*>/)[1].split('</body>')[0];
143
- } else {
144
- document.body.innerHTML = body;
145
- }
145
+ var doc = document.documentElement.cloneNode();
146
+ doc.innerHTML = body;
146
147
 
148
+ document.body.innerHTML = doc.querySelector('body').innerHTML;
147
149
  pw.component.findAndInit(document.querySelectorAll('body')[0]);
150
+
151
+ document.body.scrollTop = document.documentElement.scrollTop = 0;
148
152
  }
149
153
  });
150
154
  }
@@ -1 +1 @@
1
- function boot(){if(!window.socket)return void setTimeout(boot,100);if(window.location.hash){var t=window.location.hash.split("#:")[1].split("/"),n=t.shift(),o=t.join("/");pw.component.broadcast(n+":navigator:boot",o)}}function handleState(t,n){var o=t.uri||t.url;if(!window.socket)return void(document.location=o);if(t.context)t.r_uri=document.location.pathname+"#:"+t.context+"/"+o,window.context={_state:t,name:t.context,uri:t.r_uri,container:t.container,partial:t.partial};else if(t.r_uri=o,"default"!==window.context.name){if("back"===n)return pw.component.broadcast(window.context.name+":navigator:exit"),void(window.context={name:"default",uri:t.uri});t.r_uri=document.location.pathname+"#:"+window.context.name+"/"+o,t.context=window.context.name,t.container=window.context.container,t.partial=window.context.partial}var i={uri:o,action:"call-route",method:"get"};t.container&&(i.container=t.container),t.partial&&(i.partial=t.partial),window.socket.send(i,function(n){if(t.context)pw.component.broadcast(t.context+":navigator:enter",n);else{var o=n.body[0];o.match(/<title>/)&&(document.title=o.split(/<title>/)[1].split("</title>")[0]),o.match(/<body [^>]*>/)?document.body.innerHTML=o.split(/<body [^>]*>/)[1].split("</body>")[0]:document.body.innerHTML=o,pw.component.findAndInit(document.querySelectorAll("body")[0])}})}!function(t){if(pw.init.register(boot),t){var n=!1,o=t.pushState;t.pushState=function(i,e,a){return n=!0,"function"==typeof t.onpushstate&&t.onpushstate({state:i}),a==window.location.pathname?(pw.component.broadcast(window.context.name+":navigator:exit"),window.context={_state:i,name:"default",uri:window.location.href},i.r_uri=a):handleState(i,"forward"),o.apply(t,[i,e,i.r_uri])},window.onpopstate=function(t){if(n){var o=t.state;o||(o={}),o.uri||(o.uri=window.context.uri),handleState(o,"back")}}}}(window.history),window.context={name:"default",uri:window.location.href};
1
+ function boot(){if(!window.socket)return void setTimeout(boot,100);if(window.location.hash){var t=window.location.hash.split("#:")[1].split("/"),n=t.shift(),o=t.join("/");pw.component.broadcast(n+":navigator:boot",o)}}function handleState(t,n){var o=t.uri||t.url;if(!window.socket)return void(document.location=o);if(o=o.replace(document.location.origin,""),pw.component.broadcast("navigator:change",{uri:o}),t.context)t.r_uri=document.location.pathname+"#:"+t.context+"/"+o,window.context={_state:t,name:t.context,uri:t.r_uri,container:t.container,partial:t.partial};else if(t.r_uri=o,"default"!==window.context.name){if("back"===n)return pw.component.broadcast(window.context.name+":navigator:exit"),void(window.context={name:"default",uri:t.uri});t.r_uri=document.location.pathname+"#:"+window.context.name+"/"+o,t.context=window.context.name,t.container=window.context.container,t.partial=window.context.partial}var e={uri:o,action:"call-route",method:"get"};t.container&&(e.container=t.container),t.partial&&(e.partial=t.partial),window.socket.send(e,function(n){if(t.context)pw.component.broadcast(t.context+":navigator:enter",n);else{var o=n.body[0];if(o.match(/<title>/)){var e=o.split(/<title>/)[1].split("</title>")[0];document.querySelector("title").innerHTML=e}var i=document.documentElement.cloneNode();i.innerHTML=o,document.body.innerHTML=i.querySelector("body").innerHTML,pw.component.findAndInit(document.querySelectorAll("body")[0]),document.body.scrollTop=document.documentElement.scrollTop=0}})}!function(t){if(pw.init.register(boot),t){var n=!1,o=t.pushState;t.pushState=function(e,i,a){return n=!0,"function"==typeof t.onpushstate&&t.onpushstate({state:e}),a==window.location.pathname?(pw.component.broadcast(window.context.name+":navigator:exit"),window.context={_state:e,name:"default",uri:window.location.href},e.r_uri=a):handleState(e,"forward"),o.apply(t,[e,i,e.r_uri])},window.onpopstate=function(t){if(n){var o=t.state;o||(o={}),o.uri||(o.uri=window.context.uri),handleState(o,"back")}}}}(window.history),window.context={name:"default",uri:window.location.href};
@@ -1,5 +1,5 @@
1
1
  var pw = {
2
- version: '0.1.1'
2
+ version: '0.2.4'
3
3
  };
4
4
 
5
5
  (function() {
@@ -142,7 +142,7 @@ pw.node = {
142
142
  }
143
143
 
144
144
  var next = node.parentNode;
145
- if (next !== document) {
145
+ if (next && next !== document) {
146
146
  return pw.node.inForm(next);
147
147
  }
148
148
  },
@@ -154,7 +154,7 @@ pw.node = {
154
154
  }
155
155
 
156
156
  var next = node.parentNode;
157
- if (next !== document) {
157
+ if (next && next !== document) {
158
158
  return pw.node.component(next);
159
159
  }
160
160
  },
@@ -166,7 +166,7 @@ pw.node = {
166
166
  }
167
167
 
168
168
  var next = node.parentNode;
169
- if (next !== document) {
169
+ if (next && next !== document) {
170
170
  return pw.node.scope(next);
171
171
  }
172
172
  },
@@ -178,7 +178,7 @@ pw.node = {
178
178
  }
179
179
 
180
180
  var next = node.parentNode;
181
- if (next !== document) {
181
+ if (next && next !== document) {
182
182
  return pw.node.scopeName(next);
183
183
  }
184
184
  },
@@ -190,7 +190,7 @@ pw.node = {
190
190
  }
191
191
 
192
192
  var next = node.parentNode;
193
- if (next !== document) {
193
+ if (next && next !== document) {
194
194
  return pw.node.prop(next);
195
195
  }
196
196
  },
@@ -202,7 +202,7 @@ pw.node = {
202
202
  }
203
203
 
204
204
  var next = node.parentNode;
205
- if (next !== document) {
205
+ if (next && next !== document) {
206
206
  return pw.node.propName(next);
207
207
  }
208
208
  },
@@ -215,11 +215,11 @@ pw.node = {
215
215
  },
216
216
 
217
217
  // creates a context in which view manipulations can occur
218
- with: function(node, cb) {
218
+ invoke: function(node, cb) {
219
219
  cb.call(node);
220
220
  },
221
221
 
222
- for: function(node, data, cb) {
222
+ invokeWithData: function(node, data, cb) {
223
223
  if (pw.node.isNodeList(node)) {
224
224
  node = pw.node.toA(node);
225
225
  }
@@ -257,14 +257,14 @@ pw.node = {
257
257
  },
258
258
 
259
259
  repeat: function(node, data, cb) {
260
- pw.node.for(pw.node.match(node, data), data, cb);
260
+ pw.node.invokeWithData(pw.node.match(node, data), data, cb);
261
261
  },
262
262
 
263
263
  // binds an object to a node
264
264
  bind: function (data, node, cb) {
265
265
  var scope = pw.node.findBindings(node)[0];
266
266
 
267
- pw.node.for(node, data, function(dm) {
267
+ pw.node.invokeWithData(node, data, function(dm) {
268
268
  if (!dm) {
269
269
  return;
270
270
  }
@@ -389,7 +389,9 @@ pw.node = {
389
389
  } else if (node.tagName === 'TEXTAREA' || pw.node.isSelfClosingTag(node)) {
390
390
  node.value = value;
391
391
  } else {
392
- node.innerHTML = value;
392
+ if (value) {
393
+ node.innerHTML = value;
394
+ }
393
395
  }
394
396
  },
395
397
 
@@ -725,6 +727,12 @@ pw_State.prototype = {
725
727
 
726
728
  // gets the current represented state from the node and diffs it with the current state
727
729
  diffNode: function (node) {
730
+ if (node.hasAttribute('data-ui')) {
731
+ return {
732
+ '__nested': pw.state.build(pw.node.significant(node))
733
+ };
734
+ }
735
+
728
736
  return pw.state.build(pw.node.significant(pw.node.scope(node)))[0];
729
737
  },
730
738
 
@@ -758,7 +766,7 @@ pw_State.prototype = {
758
766
  this.snapshots.push(copy);
759
767
  },
760
768
 
761
- delete: function (state) {
769
+ remove: function (state) {
762
770
  var copy = this.copy();
763
771
  var match = copy.find(function (s) {
764
772
  return s.id === state.id;
@@ -793,8 +801,15 @@ pw.view = {
793
801
  },
794
802
 
795
803
  fromStr: function (str) {
796
- var e = document.createElement("div");
804
+ var nodeType = 'div';
805
+
806
+ if (str.match(/^<tr/) || str.match(/^<tbody/)) {
807
+ nodeType = 'table';
808
+ }
809
+
810
+ var e = document.createElement(nodeType);
797
811
  e.innerHTML = str;
812
+
798
813
  return pw.view.init(e.childNodes[0]);
799
814
  }
800
815
  };
@@ -839,16 +854,16 @@ pw_View.prototype = {
839
854
  return pw.attrs.init(this);
840
855
  },
841
856
 
842
- with: function (cb) {
843
- pw.node.with(this.node, cb);
857
+ invoke: function (cb) {
858
+ pw.node.invoke(this.node, cb);
844
859
  },
845
860
 
846
861
  match: function (data) {
847
862
  pw.node.match(this.node, data);
848
863
  },
849
864
 
850
- for: function (data, cb) {
851
- pw.node.for(this.node, data, cb);
865
+ invokeWithData: function (data, cb) {
866
+ pw.node.invokeWithData(this.node, data, cb);
852
867
  },
853
868
 
854
869
  repeat: function (data, cb) {
@@ -861,6 +876,41 @@ pw_View.prototype = {
861
876
 
862
877
  apply: function (data, cb) {
863
878
  pw.node.apply(data, this.node, cb);
879
+ },
880
+
881
+ use: function (version, cb) {
882
+ var self = this;
883
+
884
+ if (this.node.getAttribute('data-version') != version) {
885
+ this.node.setAttribute('data-version', version);
886
+
887
+ var lookup = {
888
+ scope: this.node.getAttribute('data-scope'),
889
+ version: version
890
+ };
891
+
892
+ window.socket.fetchView(lookup, function (view) {
893
+ view.node.setAttribute('data-channel', self.node.getAttribute('data-channel'));
894
+ pw.node.replace(self.node, view.node);
895
+ self.node = view.node;
896
+ cb();
897
+ });
898
+ } else {
899
+ cb();
900
+ }
901
+ },
902
+
903
+ setEndpoint: function (endpoint) {
904
+ this.endpoint = endpoint;
905
+ return this;
906
+ },
907
+
908
+ first: function () {
909
+ return this;
910
+ },
911
+
912
+ length: function () {
913
+ return 1;
864
914
  }
865
915
  };
866
916
 
@@ -875,7 +925,7 @@ pw_View.prototype = {
875
925
  });
876
926
 
877
927
  // pass through functions without view
878
- ['remove', 'clear', 'versionNode'].forEach(function (method) {
928
+ ['remove', 'clear', 'versionName'].forEach(function (method) {
879
929
  pw_View.prototype[method] = function () {
880
930
  return pw.node[method](this.node);
881
931
  };
@@ -908,7 +958,7 @@ pw.collection = {
908
958
  var pw_Collection = function (views, parent, scope) {
909
959
  this.views = views;
910
960
  this.parent = parent;
911
- this.scope = scope;
961
+ this._scope = scope;
912
962
  };
913
963
 
914
964
  pw_Collection.prototype = {
@@ -1007,11 +1057,11 @@ pw_Collection.prototype = {
1007
1057
  return pw.collection.init(prependedViews);
1008
1058
  },
1009
1059
 
1010
- with: function (cb) {
1011
- pw.node.with(this.views, cb);
1060
+ invoke: function (cb) {
1061
+ pw.node.invoke(this.views, cb);
1012
1062
  },
1013
1063
 
1014
- for: function(data, fn) {
1064
+ invokeWithData: function(data, fn) {
1015
1065
  data = Array.ensure(data);
1016
1066
 
1017
1067
  this.views.forEach(function (view, i) {
@@ -1037,12 +1087,13 @@ pw_Collection.prototype = {
1037
1087
  this.views.slice(0).forEach(function (view) {
1038
1088
  var id = view.node.getAttribute('data-id');
1039
1089
 
1040
- if (!id) {
1041
- return;
1042
- }
1043
-
1044
- if (!data.find(function (datum) { return datum.id.toString() === id })) {
1090
+ if (!id && data[0].id) {
1045
1091
  this.removeView(view);
1092
+ return;
1093
+ } else if (id) {
1094
+ if (!data.find(function (datum) { return datum.id && datum.id.toString() === id })) {
1095
+ this.removeView(view);
1096
+ }
1046
1097
  }
1047
1098
  }, this);
1048
1099
 
@@ -1083,12 +1134,12 @@ pw_Collection.prototype = {
1083
1134
 
1084
1135
  repeat: function (data, fn) {
1085
1136
  this.match(data, function () {
1086
- this.for(data, fn);
1137
+ this.invokeWithData(data, fn);
1087
1138
  });
1088
1139
  },
1089
1140
 
1090
1141
  bind: function (data, fn) {
1091
- this.for(data, function(datum) {
1142
+ this.invokeWithData(data, function(datum) {
1092
1143
  this.bind(datum);
1093
1144
 
1094
1145
  if(!(typeof fn === 'undefined')) {
@@ -1113,7 +1164,14 @@ pw_Collection.prototype = {
1113
1164
  });
1114
1165
  },
1115
1166
 
1116
- endpoint: function (endpoint) {
1167
+ version: function (data, fn) {
1168
+ var self = this;
1169
+ this.match(data, function () {
1170
+ this.invokeWithData(data, fn);
1171
+ });
1172
+ },
1173
+
1174
+ setEndpoint: function (endpoint) {
1117
1175
  this.endpoint = endpoint;
1118
1176
  return this;
1119
1177
  }
@@ -1320,11 +1378,10 @@ pw_Component.prototype = {
1320
1378
 
1321
1379
  // make it mutable
1322
1380
  var mutableCb = function (evt) {
1323
- evt.preventDefault();
1324
-
1325
1381
  var scope = pw.node.scope(evt.target);
1326
1382
 
1327
1383
  if (scope) {
1384
+ evt.preventDefault();
1328
1385
  self.mutated(scope);
1329
1386
  }
1330
1387
  };
@@ -1351,6 +1408,33 @@ pw_Component.prototype = {
1351
1408
  pw.component.deregisterForBroadcast(channel, this);
1352
1409
  },
1353
1410
 
1411
+ // Bubbles an event up to a parent component. Intended to be used
1412
+ // as an alternative to `broadcast` in cases where child components
1413
+ // have an impact on their parents.
1414
+ bubble: function (channel, payload) {
1415
+ var parentComponent = pw.node.component(this.node.parentNode);
1416
+
1417
+ (channelBroadcasts[channel] || []).forEach(function (cbTuple) {
1418
+ if (cbTuple[1].node == parentComponent) {
1419
+ cbTuple[0].call(cbTuple[1], payload);
1420
+ }
1421
+ });
1422
+ },
1423
+
1424
+ // Trickles an event down to child components. Intended to be used
1425
+ // as an alternative to `broadcast` in cases where parent components
1426
+ // have an impact on their children.
1427
+ trickle: function (channel, payload) {
1428
+ var channels = (channelBroadcasts[channel] || []);
1429
+ pw.node.toA(this.node.getElementsByTagName('*')).forEach(function (node) {
1430
+ channels.forEach(function (cbTuple) {
1431
+ if (cbTuple[1].node == node) {
1432
+ cbTuple[0].call(cbTuple[1], payload);
1433
+ }
1434
+ });
1435
+ })
1436
+ },
1437
+
1354
1438
  //TODO this is pretty similary to processing instructions
1355
1439
  // for views in that we also have to handle the empty case
1356
1440
  //
@@ -1407,7 +1491,7 @@ pw_Component.prototype = {
1407
1491
  }
1408
1492
 
1409
1493
  if (state.length > 0) {
1410
- this.view.scope(state[0].scope).endpoint(this.endpoint || this).apply(state);
1494
+ this.view.scope(state[0].scope).setEndpoint(this.endpoint || this).apply(state);
1411
1495
  } else {
1412
1496
  pw.node.breadthFirst(this.view.node, function () {
1413
1497
  if (this.hasAttribute('data-scope')) {
@@ -1435,8 +1519,8 @@ pw_Component.prototype = {
1435
1519
  }
1436
1520
  },
1437
1521
 
1438
- delete: function (data) {
1439
- this.state.delete(data);
1522
+ remove: function (data) {
1523
+ this.state.remove(data);
1440
1524
  this.transform(this.state.current());
1441
1525
  },
1442
1526
 
@@ -1470,6 +1554,7 @@ pw.init.register(function () {
1470
1554
  pw.socket.init({
1471
1555
  cb: function (socket) {
1472
1556
  window.socket = socket;
1557
+ pw.component.broadcast('socket:available');
1473
1558
  }
1474
1559
  });
1475
1560
  });
@@ -1671,7 +1756,9 @@ pw.instruct = {
1671
1756
  });
1672
1757
  },
1673
1758
 
1674
- // TODO: make this smart and cache results
1759
+ // TODO: make this smart and cache results, invalidating
1760
+ // if the websocket connection reconnects (since that means
1761
+ // the server probably restarted)
1675
1762
  template: function (view, cb) {
1676
1763
  var lookup = {};
1677
1764
 
@@ -1696,23 +1783,46 @@ pw.instruct = {
1696
1783
  });
1697
1784
  },
1698
1785
 
1699
- perform: function (collection, instructions) {
1786
+ perform: function (collection, instructions, cb) {
1700
1787
  var self = this;
1788
+ instructions = instructions || [];
1701
1789
 
1702
- (instructions || []).forEach(function (instruction, i) {
1790
+ function instruct (subject, instruction) {
1703
1791
  var method = instruction[0];
1704
1792
  var value = instruction[1];
1705
1793
  var nested = instruction[2];
1706
1794
 
1795
+ // remap instructions to the ring name
1796
+ if (method === 'with') {
1797
+ method = 'invoke';
1798
+ }
1799
+
1800
+ if (method === 'for') {
1801
+ method = 'invokeWithData';
1802
+ }
1803
+
1707
1804
  if (collection[method]) {
1708
- if (method == 'with' || method == 'for' || method == 'bind' || method == 'repeat' || method == 'apply') {
1709
- collection.endpoint(self)[method].call(collection, value, function (datum) {
1710
- pw.instruct.perform(this, nested[value.indexOf(datum)]);
1805
+ if (method == 'invoke' || method == 'invokeWithData' || method == 'bind' || method == 'repeat' || method == 'apply' || method == 'version') {
1806
+ var cbLength = collection.length();
1807
+ var cbCount = 0;
1808
+ var nestedCb = function () {
1809
+ cbCount++;
1810
+
1811
+ if (cbCount == cbLength) {
1812
+ next();
1813
+ }
1814
+ }
1815
+ collection.setEndpoint(self)[method].call(collection, value, function (datum) {
1816
+ pw.instruct.perform(this, nested[value.indexOf(datum)], nestedCb);
1711
1817
  });
1712
1818
  return;
1713
1819
  } else if (method == 'attrs') {
1714
1820
  self.performAttr(collection.attrs(), nested);
1715
1821
  return;
1822
+ } else if (method == 'use') {
1823
+ collection.setEndpoint(self);
1824
+ collection.use(value, next);
1825
+ return;
1716
1826
  } else {
1717
1827
  var mutatedViews = collection[method].call(collection, value);
1718
1828
  }
@@ -1722,13 +1832,33 @@ pw.instruct = {
1722
1832
  }
1723
1833
 
1724
1834
  if (nested instanceof Array) {
1725
- pw.instruct.perform(mutatedViews, nested);
1835
+ pw.instruct.perform(mutatedViews, nested, next);
1836
+ return;
1726
1837
  } else if (mutatedViews) {
1727
1838
  collection = mutatedViews;
1728
1839
  }
1729
- });
1730
1840
 
1731
- pw.component.findAndInit(collection.node);
1841
+ next();
1842
+ };
1843
+
1844
+ var i = 0;
1845
+ function next() {
1846
+ if (i < instructions.length) {
1847
+ instruct(collection, instructions[i++]);
1848
+ } else {
1849
+ done();
1850
+ }
1851
+ };
1852
+
1853
+ function done() {
1854
+ if (cb) {
1855
+ cb();
1856
+ } else {
1857
+ pw.component.findAndInit(collection.node);
1858
+ }
1859
+ };
1860
+
1861
+ next();
1732
1862
  },
1733
1863
 
1734
1864
  performAttr: function (context, attrInstructions) {