├── web
├── views
│ ├── page_view.ex
│ ├── comment_view.ex
│ ├── field_view.ex
│ ├── layout_view.ex
│ ├── error_view.ex
│ └── error_helpers.ex
├── static
│ ├── assets
│ │ ├── favicon.ico
│ │ ├── images
│ │ │ └── phoenix.png
│ │ └── robots.txt
│ └── js
│ │ ├── app.js
│ │ └── socket.js
├── admin
│ ├── field.ex
│ ├── tag.ex
│ ├── dashboard.ex
│ ├── comment.ex
│ └── post.ex
├── controllers
│ ├── page_controller.ex
│ ├── field_controller.ex
│ └── comment_controller.ex
├── templates
│ ├── field
│ │ ├── new.html.eex
│ │ ├── edit.html.eex
│ │ ├── show.html.eex
│ │ ├── index.html.eex
│ │ └── form.html.eex
│ ├── comment
│ │ ├── new.html.eex
│ │ ├── edit.html.eex
│ │ ├── show.html.eex
│ │ ├── form.html.eex
│ │ └── index.html.eex
│ ├── page
│ │ └── index.html.eex
│ └── layout
│ │ └── app.html.eex
├── models
│ ├── post_tag.ex
│ ├── tag.ex
│ ├── comment.ex
│ ├── field.ex
│ └── post.ex
├── router.ex
├── gettext.ex
├── channels
│ └── user_socket.ex
└── web.ex
├── priv
├── static
│ ├── favicon.ico
│ ├── images
│ │ ├── phoenix.png
│ │ ├── glyphicons-halflings.png
│ │ ├── active_admin
│ │ │ ├── orderable.png
│ │ │ ├── admin_notes_icon.png
│ │ │ └── datepicker
│ │ │ │ ├── datepicker-nipple.png
│ │ │ │ ├── datepicker-header-bg.png
│ │ │ │ ├── datepicker-input-icon.png
│ │ │ │ ├── datepicker-next-link-icon.png
│ │ │ │ └── datepicker-prev-link-icon.png
│ │ └── glyphicons-halflings-white.png
│ ├── robots.txt
│ └── js
│ │ ├── best_in_place.purr.js
│ │ ├── active_admin.js
│ │ ├── jquery-ujs.js.js
│ │ ├── active_admin_lib.js
│ │ ├── best_in_place.js
│ │ ├── app.js.map
│ │ └── app.js
├── repo
│ ├── migrations
│ │ ├── 20160118161331_create_tag.exs
│ │ ├── 20160119234451_add_published_to_post.exs
│ │ ├── 20160118161706_create_post_tag.exs
│ │ ├── 20160113021216_create_post.exs
│ │ ├── 20160120001255_create_comment.exs
│ │ └── 20160121042204_create_field.exs
│ └── seeds.exs
└── gettext
│ ├── errors.pot
│ └── en
│ └── LC_MESSAGES
│ └── errors.po
├── test
├── views
│ ├── layout_view_test.exs
│ ├── page_view_test.exs
│ └── error_view_test.exs
├── test_helper.exs
├── controllers
│ ├── page_controller_test.exs
│ ├── comment_controller_test.exs
│ └── field_controller_test.exs
├── models
│ ├── tag_test.exs
│ ├── post_tag_test.exs
│ ├── post_test.exs
│ ├── comment_test.exs
│ └── field_test.exs
└── support
│ ├── channel_case.ex
│ ├── conn_case.ex
│ └── model_case.ex
├── .iex.exs
├── lib
├── admin_demo
│ ├── repo.ex
│ └── endpoint.ex
└── admin_demo.ex
├── package.json
├── config
├── test.exs
├── dev.exs
├── config.exs
└── prod.exs
├── .gitignore
├── README.md
├── mix.lock
├── mix.exs
└── brunch-config.js
/web/views/page_view.ex:
--------------------------------------------------------------------------------
1 | defmodule AdminDemo.PageView do
2 | use AdminDemo.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/web/views/comment_view.ex:
--------------------------------------------------------------------------------
1 | defmodule AdminDemo.CommentView do
2 | use AdminDemo.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/web/views/field_view.ex:
--------------------------------------------------------------------------------
1 | defmodule AdminDemo.FieldView do
2 | use AdminDemo.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/web/views/layout_view.ex:
--------------------------------------------------------------------------------
1 | defmodule AdminDemo.LayoutView do
2 | use AdminDemo.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/priv/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smpallen99/admin_demo/master/priv/static/favicon.ico
--------------------------------------------------------------------------------
/test/views/layout_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule AdminDemo.LayoutViewTest do
2 | use AdminDemo.ConnCase, async: true
3 | end
--------------------------------------------------------------------------------
/web/static/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smpallen99/admin_demo/master/web/static/assets/favicon.ico
--------------------------------------------------------------------------------
/priv/static/images/phoenix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smpallen99/admin_demo/master/priv/static/images/phoenix.png
--------------------------------------------------------------------------------
/test/views/page_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule AdminDemo.PageViewTest do
2 | use AdminDemo.ConnCase, async: true
3 | end
4 |
--------------------------------------------------------------------------------
/web/static/assets/images/phoenix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smpallen99/admin_demo/master/web/static/assets/images/phoenix.png
--------------------------------------------------------------------------------
/.iex.exs:
--------------------------------------------------------------------------------
1 | alias AdminDemo.Repo
2 | alias AdminDemo.Post
3 | alias AdminDemo.Tag
4 | alias AdminDemo.PostTag
5 | alias AdminDemo.Comment
6 |
7 |
--------------------------------------------------------------------------------
/lib/admin_demo/repo.ex:
--------------------------------------------------------------------------------
1 | defmodule AdminDemo.Repo do
2 | use Ecto.Repo, otp_app: :admin_demo
3 | use Scrivener, page_size: 10
4 | end
5 |
--------------------------------------------------------------------------------
/priv/static/images/glyphicons-halflings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smpallen99/admin_demo/master/priv/static/images/glyphicons-halflings.png
--------------------------------------------------------------------------------
/priv/static/images/active_admin/orderable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smpallen99/admin_demo/master/priv/static/images/active_admin/orderable.png
--------------------------------------------------------------------------------
/web/admin/field.ex:
--------------------------------------------------------------------------------
1 | defmodule AdminDemo.ExAdmin.Field do
2 | use ExAdmin.Register
3 |
4 | register_resource AdminDemo.Field do
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/priv/static/images/glyphicons-halflings-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smpallen99/admin_demo/master/priv/static/images/glyphicons-halflings-white.png
--------------------------------------------------------------------------------
/priv/static/images/active_admin/admin_notes_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smpallen99/admin_demo/master/priv/static/images/active_admin/admin_notes_icon.png
--------------------------------------------------------------------------------
/priv/static/images/active_admin/datepicker/datepicker-nipple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smpallen99/admin_demo/master/priv/static/images/active_admin/datepicker/datepicker-nipple.png
--------------------------------------------------------------------------------
/priv/static/images/active_admin/datepicker/datepicker-header-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smpallen99/admin_demo/master/priv/static/images/active_admin/datepicker/datepicker-header-bg.png
--------------------------------------------------------------------------------
/priv/static/images/active_admin/datepicker/datepicker-input-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smpallen99/admin_demo/master/priv/static/images/active_admin/datepicker/datepicker-input-icon.png
--------------------------------------------------------------------------------
/priv/static/images/active_admin/datepicker/datepicker-next-link-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smpallen99/admin_demo/master/priv/static/images/active_admin/datepicker/datepicker-next-link-icon.png
--------------------------------------------------------------------------------
/priv/static/images/active_admin/datepicker/datepicker-prev-link-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smpallen99/admin_demo/master/priv/static/images/active_admin/datepicker/datepicker-prev-link-icon.png
--------------------------------------------------------------------------------
/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start
2 |
3 | Mix.Task.run "ecto.create", ~w(-r AdminDemo.Repo --quiet)
4 | Mix.Task.run "ecto.migrate", ~w(-r AdminDemo.Repo --quiet)
5 | Ecto.Adapters.SQL.begin_test_transaction(AdminDemo.Repo)
6 |
7 |
--------------------------------------------------------------------------------
/web/controllers/page_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule AdminDemo.PageController do
2 | use AdminDemo.Web, :controller
3 |
4 | def index(conn, _params) do
5 |
6 | render conn, "index.html", something: "Testing..."
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/web/templates/field/new.html.eex:
--------------------------------------------------------------------------------
1 |
9 | <%= label f, :body, class: "control-label" %>
10 | <%= text_input f, :body, class: "form-control" %>
11 | <%= error_tag f, :body %>
12 |
13 |
14 |
15 | <%= label f, :approved, class: "control-label" %>
16 | <%= checkbox f, :approved, class: "form-control" %>
17 | <%= error_tag f, :approved %>
18 |
19 |
20 |
21 | <%= label f, :show_at, class: "control-label" %>
22 | <%= datetime_select f, :show_at, class: "form-control" %>
23 | <%= error_tag f, :show_at %>
24 |
25 |
26 |
9 | <%= label f, :string_field, class: "control-label" %>
10 | <%= text_input f, :string_field, class: "form-control" %>
11 | <%= error_tag f, :string_field %>
12 |
13 |
14 |
15 | <%= label f, :uuid_field, class: "control-label" %>
16 | <%= text_input f, :uuid_field, class: "form-control" %>
17 | <%= error_tag f, :uuid_field %>
18 |
19 |
20 |
21 | <%= label f, :datetime_field, class: "control-label" %>
22 | <%= datetime_select f, :datetime_field, class: "form-control" %>
23 | <%= error_tag f, :datetime_field %>
24 |
25 |
26 |
27 | <%= label f, :date_field, class: "control-label" %>
28 | <%= date_select f, :date_field, class: "form-control" %>
29 | <%= error_tag f, :date_field %>
30 |
31 |
32 |
33 | <%= label f, :time_field, class: "control-label" %>
34 | <%= time_select f, :time_field, class: "form-control" %>
35 | <%= error_tag f, :time_field %>
36 |
37 |
38 |
39 | <%= label f, :text_field, class: "control-label" %>
40 | <%= textarea f, :text_field, class: "form-control" %>
41 | <%= error_tag f, :text_field %>
42 |
43 |
44 | ").addClass("flash flash_" + type).text(message);
321 | jQuery(".flashes").append(this.reference);
322 | if (close_after != null) {
323 | this.close_after(close_after);
324 | }
325 | }
326 |
327 | Flash.prototype.close_after = function(close_after) {
328 | return setTimeout((function(_this) {
329 | return function() {
330 | return _this.close();
331 | };
332 | })(this), close_after * 1000);
333 | };
334 |
335 | Flash.prototype.close = function() {
336 | return this.reference.remove();
337 | };
338 |
339 | return Flash;
340 |
341 | })();
342 |
343 | }).call(this);
344 |
345 |
346 | // File: has_many.js.js
347 | // Generated by CoffeeScript 1.10.0
348 | (function() {
349 | var init_sortable, recompute_positions;
350 |
351 | $(function() {
352 | $(document).on('click', 'a.button.has_many_remove', function(e) {
353 | var parent, to_remove;
354 | e.preventDefault();
355 | parent = $(this).closest('.has_many_container');
356 | to_remove = $(this).closest('fieldset');
357 | recompute_positions(parent);
358 | parent.trigger('has_many_remove:before', [to_remove, parent]);
359 | to_remove.remove();
360 | return parent.trigger('has_many_remove:after', [to_remove, parent]);
361 | });
362 | $(document).on('click', 'a.button.has_many_add', function(e) {
363 | var before_add, fieldset, html, index, parent, regex;
364 | e.preventDefault();
365 | parent = $(this).closest('.has_many_container');
366 | parent.trigger(before_add = $.Event('has_many_add:before'), [parent]);
367 | if (!before_add.isDefaultPrevented()) {
368 | index = parent.data('has_many_index') || parent.children('fieldset').length - 1;
369 | parent.data({
370 | has_many_index: ++index
371 | });
372 | regex = new RegExp($(this).data('placeholder'), 'g');
373 | html = $(this).data('html').replace(regex, index);
374 | fieldset = $(html).insertBefore(this);
375 | recompute_positions(parent);
376 | return parent.trigger('has_many_add:after', [fieldset, parent]);
377 | }
378 | });
379 | $(document).on('change', '.has_many_container[data-sortable] :input[name$="[_destroy]"]', function() {
380 | return recompute_positions($(this).closest('.has_many'));
381 | });
382 | init_sortable();
383 | return $(document).on('has_many_add:after', '.has_many_container', init_sortable);
384 | });
385 |
386 | init_sortable = function() {
387 | var elems;
388 | elems = $('.has_many_container[data-sortable]:not(.ui-sortable)');
389 | elems.sortable({
390 | items: '> fieldset',
391 | handle: '> ol > .handle',
392 | stop: recompute_positions
393 | });
394 | return elems.each(recompute_positions);
395 | };
396 |
397 | recompute_positions = function(parent) {
398 | var input_name, position;
399 | parent = parent instanceof jQuery ? parent : $(this);
400 | input_name = parent.data('sortable');
401 | position = parseInt(parent.data('sortable-start') || 0, 10);
402 | return parent.children('fieldset').each(function() {
403 | var destroy_input, sortable_input;
404 | destroy_input = $(this).find("> ol > .input > :input[name$='[_destroy]']");
405 | sortable_input = $(this).find("> ol > .input > :input[name$='[" + input_name + "]']");
406 | if (sortable_input.length) {
407 | return sortable_input.val(destroy_input.is(':checked') ? '' : position++);
408 | }
409 | });
410 | };
411 |
412 | }).call(this);
413 |
414 |
415 | // File: modal_dialog.js.js
416 | // Generated by CoffeeScript 1.10.0
417 | (function() {
418 | ActiveAdmin.modal_dialog = function(message, inputs, callback) {
419 | var elem, html, klass, name, opts, ref, ref1, type, v, wrapper;
420 | html = "
";
449 | return $(html).appendTo('body').dialog({
450 | modal: true,
451 | dialogClass: 'active_admin_dialog',
452 | buttons: {
453 | OK: function() {
454 | callback($(this).serializeObject());
455 | return $(this).dialog('close');
456 | },
457 | Cancel: function() {
458 | return $(this).dialog('close').remove();
459 | }
460 | }
461 | });
462 | };
463 |
464 | }).call(this);
465 |
466 |
467 | // File: per_page.js.js
468 | // Generated by CoffeeScript 1.10.0
469 | (function() {
470 | ActiveAdmin.PerPage = (function() {
471 | function PerPage(options, element) {
472 | this.options = options;
473 | this.element = element;
474 | this.$element = $(this.element);
475 | this._init();
476 | this._bind();
477 | }
478 |
479 | PerPage.prototype._init = function() {
480 | return this.$params = this._queryParams();
481 | };
482 |
483 | PerPage.prototype._bind = function() {
484 | return this.$element.change((function(_this) {
485 | return function() {
486 | _this.$params['per_page'] = _this.$element.val();
487 | delete _this.$params['page'];
488 | return location.search = $.param(_this.$params);
489 | };
490 | })(this));
491 | };
492 |
493 | PerPage.prototype._queryParams = function() {
494 | var m, params, query, re;
495 | query = window.location.search.substring(1);
496 | params = {};
497 | re = /([^&=]+)=([^&]*)/g;
498 | while (m = re.exec(query)) {
499 | params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
500 | }
501 | return params;
502 | };
503 |
504 | return PerPage;
505 |
506 | })();
507 |
508 | $.widget.bridge('perPage', ActiveAdmin.PerPage);
509 |
510 | $(function() {
511 | return $('.pagination_per_page select').perPage();
512 | });
513 |
514 | }).call(this);
515 |
516 |
517 | // File: popover.js.js
518 | // Generated by CoffeeScript 1.10.0
519 | (function() {
520 | ActiveAdmin.Popover = (function() {
521 | function Popover(options1, element) {
522 | var defaults;
523 | this.options = options1;
524 | this.element = element;
525 | this.$element = $(this.element);
526 | defaults = {
527 | fadeInDuration: 20,
528 | fadeOutDuration: 100,
529 | autoOpen: true,
530 | pageWrapperElement: "#wrapper",
531 | onClickActionItemCallback: null
532 | };
533 | this.options = $.extend(defaults, options);
534 | this.isOpen = false;
535 | if (!(this.$popover = $(this.$element.attr('href'))).length) {
536 | this.$popover = this.$element.next('.popover');
537 | }
538 | this._buildPopover();
539 | this._bind();
540 | }
541 |
542 | Popover.prototype.open = function() {
543 | this.isOpen = true;
544 | this.$popover.fadeIn(this.options.fadeInDuration);
545 | this._positionPopover();
546 | this._positionNipple();
547 | return this;
548 | };
549 |
550 | Popover.prototype.close = function() {
551 | this.isOpen = false;
552 | this.$popover.fadeOut(this.options.fadeOutDuration);
553 | return this;
554 | };
555 |
556 | Popover.prototype.destroy = function() {
557 | this.$element.removeData('popover');
558 | this.$element.unbind();
559 | this.$element = null;
560 | return this;
561 | };
562 |
563 | Popover.prototype._buildPopover = function() {
564 | this.$nipple = $('
');
565 | this.$popover.prepend(this.$nipple);
566 | this.$popover.hide();
567 | return this.$popover.addClass('popover');
568 | };
569 |
570 | Popover.prototype._bind = function() {
571 | $(this.options.pageWrapperElement).click((function(_this) {
572 | return function() {
573 | if (_this.isOpen) {
574 | return _this.close();
575 | }
576 | };
577 | })(this));
578 | if (this.options.autoOpen) {
579 | return this.$element.click((function(_this) {
580 | return function(e) {
581 | e.stopPropagation();
582 | if (_this.isOpen) {
583 | return _this.close();
584 | } else {
585 | return _this.open();
586 | }
587 | };
588 | })(this));
589 | }
590 | };
591 |
592 | Popover.prototype._positionPopover = function() {
593 | var button_center, popover_center;
594 | button_center = this.$element.offset().left + this.$element.outerWidth() / 2;
595 | popover_center = this.$popover.outerWidth() / 2;
596 | return this.$popover.css('left', button_center - popover_center);
597 | };
598 |
599 | Popover.prototype._positionNipple = function() {
600 | this.$popover.css('top', this.$element.offset().top + this.$element.outerHeight() + 10);
601 | return this.$nipple.css('left', this.$popover.outerWidth() / 2 - this.$nipple.outerWidth() / 2);
602 | };
603 |
604 | return Popover;
605 |
606 | })();
607 |
608 | $.widget.bridge('popover', ActiveAdmin.Popover);
609 |
610 | }).call(this);
611 |
612 |
613 | // File: table-checkbox-toggler.js.js
614 | // Generated by CoffeeScript 1.10.0
615 | (function() {
616 | var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
617 | hasProp = {}.hasOwnProperty;
618 |
619 | ActiveAdmin.TableCheckboxToggler = (function(superClass) {
620 | extend(TableCheckboxToggler, superClass);
621 |
622 | function TableCheckboxToggler() {
623 | return TableCheckboxToggler.__super__.constructor.apply(this, arguments);
624 | }
625 |
626 | TableCheckboxToggler.prototype._init = function() {
627 | return TableCheckboxToggler.__super__._init.apply(this, arguments);
628 | };
629 |
630 | TableCheckboxToggler.prototype._bind = function() {
631 | TableCheckboxToggler.__super__._bind.apply(this, arguments);
632 | return this.$container.find('tbody td').click((function(_this) {
633 | return function(e) {
634 | if (e.target.type !== 'checkbox') {
635 | return _this._didClickCell(e.target);
636 | }
637 | };
638 | })(this));
639 | };
640 |
641 | TableCheckboxToggler.prototype._didChangeCheckbox = function(checkbox) {
642 | var $row;
643 | TableCheckboxToggler.__super__._didChangeCheckbox.apply(this, arguments);
644 | $row = $(checkbox).parents('tr');
645 | if (checkbox.checked) {
646 | return $row.addClass('selected');
647 | } else {
648 | return $row.removeClass('selected');
649 | }
650 | };
651 |
652 | TableCheckboxToggler.prototype._didClickCell = function(cell) {
653 | return $(cell).parent('tr').find(':checkbox').click();
654 | };
655 |
656 | return TableCheckboxToggler;
657 |
658 | })(ActiveAdmin.CheckboxToggler);
659 |
660 | $.widget.bridge('tableCheckboxToggler', ActiveAdmin.TableCheckboxToggler);
661 |
662 | }).call(this);
663 |
664 |
665 |
--------------------------------------------------------------------------------
/priv/static/js/best_in_place.js:
--------------------------------------------------------------------------------
1 | /*
2 | BestInPlace (for jQuery)
3 | version: 0.1.0 (01/01/2011)
4 | @requires jQuery >= v1.4
5 | @requires jQuery.purr to display pop-up windows
6 |
7 | By Bernat Farrero based on the work of Jan Varwig.
8 | Examples at http://bernatfarrero.com
9 |
10 | Licensed under the MIT:
11 | http://www.opensource.org/licenses/mit-license.php
12 |
13 | Usage:
14 |
15 | Attention.
16 | The format of the JSON object given to the select inputs is the following:
17 | [["key", "value"],["key", "value"]]
18 | The format of the JSON object given to the checkbox inputs is the following:
19 | ["falseValue", "trueValue"]
20 | */
21 |
22 | function BestInPlaceEditor(e) {
23 | this.element = e;
24 | this.initOptions();
25 | this.bindForm();
26 | this.initNil();
27 | jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
28 | }
29 |
30 | BestInPlaceEditor.prototype = {
31 | // Public Interface Functions //////////////////////////////////////////////
32 |
33 | activate : function() {
34 | var to_display = "";
35 | if (this.isNil) {
36 | to_display = "";
37 | }
38 | else if (this.original_content) {
39 | to_display = this.original_content;
40 | }
41 | else {
42 | if (this.sanitize) {
43 | to_display = this.element.text();
44 | } else {
45 | to_display = this.element.html();
46 | }
47 | }
48 |
49 | var elem = this.isNil ? "-" : this.sanitize ? this.element.text() : this.element.html();
50 | this.oldValue = elem;
51 | this.display_value = to_display;
52 | jQuery(this.activator).unbind("click", this.clickHandler);
53 | this.activateForm();
54 | this.element.trigger(jQuery.Event("best_in_place:activate"));
55 | },
56 |
57 | abort : function() {
58 | if (this.isNil) this.element.text(this.nil);
59 | else this.element.text(this.oldValue);
60 | jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
61 | this.element.trigger(jQuery.Event("best_in_place:abort"));
62 | this.element.trigger(jQuery.Event("best_in_place:deactivate"));
63 | },
64 |
65 | abortIfConfirm : function () {
66 | if (!this.useConfirm) {
67 | this.abort();
68 | return;
69 | }
70 |
71 | if (confirm("Are you sure you want to discard your changes?")) {
72 | this.abort();
73 | }
74 | },
75 |
76 | update : function() {
77 | var editor = this;
78 | if (this.formType in {"input":1, "textarea":1} && this.getValue() == this.oldValue)
79 | { // Avoid request if no change is made
80 | this.abort();
81 | return true;
82 | }
83 | this.isNil = false;
84 | editor.ajax({
85 | "type" : "post",
86 | "dataType" : "text",
87 | "data" : editor.requestData(),
88 | "success" : function(data){ editor.loadSuccessCallback(data); },
89 | "error" : function(request, error){ editor.loadErrorCallback(request, error); }
90 | });
91 | if (this.formType == "select") {
92 | var value = this.getValue();
93 | this.previousCollectionValue = value;
94 |
95 | jQuery.each(this.values, function(i, v) {
96 | if (value == v[0]) {
97 | editor.element.html(v[1]);
98 | }
99 | }
100 | );
101 | } else if (this.formType == "checkbox") {
102 | editor.element.html(this.getValue() ? this.values[1] : this.values[0]);
103 | } else {
104 | editor.element.text(this.getValue() !== "" ? this.getValue() : this.nil);
105 | }
106 | editor.element.trigger(jQuery.Event("best_in_place:update"));
107 | },
108 |
109 | activateForm : function() {
110 | alert("The form was not properly initialized. activateForm is unbound");
111 | },
112 |
113 | // Helper Functions ////////////////////////////////////////////////////////
114 |
115 | initOptions : function() {
116 | // Try parent supplied info
117 | var self = this;
118 | self.element.parents().each(function(){
119 | $parent = jQuery(this);
120 | self.url = self.url || $parent.attr("data-url");
121 | self.collection = self.collection || $parent.attr("data-collection");
122 | self.formType = self.formType || $parent.attr("data-type");
123 | self.objectName = self.objectName || $parent.attr("data-object");
124 | self.attributeName = self.attributeName || $parent.attr("data-attribute");
125 | self.activator = self.activator || $parent.attr("data-activator");
126 | self.okButton = self.okButton || $parent.attr("data-ok-button");
127 | self.cancelButton = self.cancelButton || $parent.attr("data-cancel-button");
128 | self.nil = self.nil || $parent.attr("data-nil");
129 | self.inner_class = self.inner_class || $parent.attr("data-inner-class");
130 | self.html_attrs = self.html_attrs || $parent.attr("data-html-attrs");
131 | self.original_content = self.original_content || $parent.attr("data-original-content");
132 | self.collectionValue = self.collectionValue || $parent.attr("data-value");
133 | });
134 |
135 | // Try Rails-id based if parents did not explicitly supply something
136 | self.element.parents().each(function(){
137 | var res = this.id.match(/^(\w+)_(\d+)$/i);
138 | if (res) {
139 | self.objectName = self.objectName || res[1];
140 | }
141 | });
142 |
143 | // Load own attributes (overrides all others)
144 | self.url = self.element.attr("data-url") || self.url || document.location.pathname;
145 | self.collection = self.element.attr("data-collection") || self.collection;
146 | self.formType = self.element.attr("data-type") || self.formtype || "input";
147 | self.objectName = self.element.attr("data-object") || self.objectName;
148 | self.attributeName = self.element.attr("data-attribute") || self.attributeName;
149 | self.activator = self.element.attr("data-activator") || self.element;
150 | self.okButton = self.element.attr("data-ok-button") || self.okButton;
151 | self.cancelButton = self.element.attr("data-cancel-button") || self.cancelButton;
152 | self.nil = self.element.attr("data-nil") || self.nil || "-";
153 | self.inner_class = self.element.attr("data-inner-class") || self.inner_class || null;
154 | self.html_attrs = self.element.attr("data-html-attrs") || self.html_attrs;
155 | self.original_content = self.element.attr("data-original-content") || self.original_content;
156 | self.collectionValue = self.element.attr("data-value") || self.collectionValue;
157 |
158 | if (!self.element.attr("data-sanitize")) {
159 | self.sanitize = true;
160 | }
161 | else {
162 | self.sanitize = (self.element.attr("data-sanitize") == "true");
163 | }
164 |
165 | if (!self.element.attr("data-use-confirm")) {
166 | self.useConfirm = true;
167 | } else {
168 | self.useConfirm = (self.element.attr("data-use-confirm") != "false");
169 | }
170 |
171 | if ((self.formType == "select" || self.formType == "checkbox") && self.collection !== null)
172 | {
173 | self.values = jQuery.parseJSON(self.collection);
174 | }
175 |
176 | },
177 |
178 | bindForm : function() {
179 | this.activateForm = BestInPlaceEditor.forms[this.formType].activateForm;
180 | this.getValue = BestInPlaceEditor.forms[this.formType].getValue;
181 | },
182 |
183 | initNil: function() {
184 | if (this.element.text() === "")
185 | {
186 | this.isNil = true;
187 | this.element.text(this.nil);
188 | }
189 | },
190 |
191 | getValue : function() {
192 | alert("The form was not properly initialized. getValue is unbound");
193 | },
194 |
195 | // Trim and Strips HTML from text
196 | sanitizeValue : function(s) {
197 | return jQuery.trim(s);
198 | },
199 |
200 | /* Generate the data sent in the POST request */
201 | requestData : function() {
202 | // To prevent xss attacks, a csrf token must be defined as a meta attribute
203 | csrf_token = jQuery('meta[name=csrf-token]').attr('content');
204 | csrf_param = jQuery('meta[name=csrf-param]').attr('content');
205 |
206 | var data = "_method=put";
207 | data += "&" + this.objectName + '[' + this.attributeName + ']=' + encodeURIComponent(this.getValue());
208 |
209 | if (csrf_param !== undefined && csrf_token !== undefined) {
210 | data += "&" + csrf_param + "=" + encodeURIComponent(csrf_token);
211 | }
212 | return data;
213 | },
214 |
215 | ajax : function(options) {
216 | options.url = this.url;
217 | options.beforeSend = function(xhr){ xhr.setRequestHeader("Accept", "application/json"); };
218 | return jQuery.ajax(options);
219 | },
220 |
221 | // Handlers ////////////////////////////////////////////////////////////////
222 |
223 | loadSuccessCallback : function(data) {
224 | try {
225 | var response = jQuery.parseJSON(jQuery.trim(data));
226 | if (response !== null && response.hasOwnProperty("display_as")) {
227 | this.element.attr("data-original-content", this.element.text());
228 | this.original_content = this.element.text();
229 | this.element.text(response["display_as"]);
230 | }
231 | this.element.trigger(jQuery.Event("ajax:success"), data);
232 |
233 | // Binding back after being clicked
234 | jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
235 | this.element.trigger(jQuery.Event("best_in_place:deactivate"));
236 |
237 | if (this.collectionValue !== null) {
238 | this.collectionValue = this.previousCollectionValue;
239 | this.previousCollectionValue = null;
240 | }
241 | }
242 | catch(err) {}
243 | },
244 |
245 | loadErrorCallback : function(request, error) {
246 | this.element.text(this.oldValue);
247 |
248 | this.element.trigger(jQuery.Event("best_in_place:error"), [request, error])
249 | this.element.trigger(jQuery.Event("ajax:error"));
250 |
251 | // Binding back after being clicked
252 | jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
253 | this.element.trigger(jQuery.Event("best_in_place:deactivate"));
254 | },
255 |
256 | clickHandler : function(event) {
257 | event.preventDefault();
258 | event.data.editor.activate();
259 | },
260 |
261 | setHtmlAttributes : function() {
262 | var formField = this.element.find(this.formType);
263 |
264 | try {
265 | var attrs = jQuery.parseJSON(this.html_attrs);
266 | for(var key in attrs){
267 | formField.attr(key, attrs[key]);
268 | }
269 | }
270 | catch(err) {}
271 |
272 | }
273 | };
274 |
275 |
276 | // Button cases:
277 | // If no buttons, then blur saves, ESC cancels
278 | // If just Cancel button, then blur saves, ESC or clicking Cancel cancels (careful of blur event!)
279 | // If just OK button, then clicking OK saves (careful of blur event!), ESC or blur cancels
280 | // If both buttons, then clicking OK saves, ESC or clicking Cancel or blur cancels
281 | BestInPlaceEditor.forms = {
282 | "input" : {
283 | activateForm : function() {
284 | var output = jQuery(document.createElement('form'))
285 | .addClass('form_in_place')
286 | .attr('action', 'javascript:void(0);')
287 | .attr('style', 'display:inline');
288 | var input_elt = jQuery(document.createElement('input'))
289 | .attr('type', 'text')
290 | .attr('name', this.attributeName)
291 | .val(this.display_value);
292 | if(this.inner_class !== null) {
293 | input_elt.addClass(this.inner_class);
294 | }
295 | output.append(input_elt);
296 | if(this.okButton) {
297 | output.append(
298 | jQuery(document.createElement('input'))
299 | .attr('type', 'submit')
300 | .attr('value', this.okButton)
301 | )
302 | }
303 | if(this.cancelButton) {
304 | output.append(
305 | jQuery(document.createElement('input'))
306 | .attr('type', 'button')
307 | .attr('value', this.cancelButton)
308 | )
309 | }
310 |
311 | this.element.html(output);
312 | this.setHtmlAttributes();
313 | this.element.find("input[type='text']")[0].select();
314 | this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler);
315 | if (this.cancelButton) {
316 | this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.input.cancelButtonHandler);
317 | }
318 | this.element.find("input[type='text']").bind('blur', {editor: this}, BestInPlaceEditor.forms.input.inputBlurHandler);
319 | this.element.find("input[type='text']").bind('keyup', {editor: this}, BestInPlaceEditor.forms.input.keyupHandler);
320 | this.blurTimer = null;
321 | this.userClicked = false;
322 | },
323 |
324 | getValue : function() {
325 | return this.sanitizeValue(this.element.find("input").val());
326 | },
327 |
328 | // When buttons are present, use a timer on the blur event to give precedence to clicks
329 | inputBlurHandler : function(event) {
330 | if (event.data.editor.okButton) {
331 | event.data.editor.blurTimer = setTimeout(function () {
332 | if (!event.data.editor.userClicked) {
333 | event.data.editor.abort();
334 | }
335 | }, 500);
336 | } else {
337 | if (event.data.editor.cancelButton) {
338 | event.data.editor.blurTimer = setTimeout(function () {
339 | if (!event.data.editor.userClicked) {
340 | event.data.editor.update();
341 | }
342 | }, 500);
343 | } else {
344 | event.data.editor.update();
345 | }
346 | }
347 | },
348 |
349 | submitHandler : function(event) {
350 | event.data.editor.userClicked = true;
351 | clearTimeout(event.data.editor.blurTimer);
352 | event.data.editor.update();
353 | },
354 |
355 | cancelButtonHandler : function(event) {
356 | event.data.editor.userClicked = true;
357 | clearTimeout(event.data.editor.blurTimer);
358 | event.data.editor.abort();
359 | event.stopPropagation(); // Without this, click isn't handled
360 | },
361 |
362 | keyupHandler : function(event) {
363 | if (event.keyCode == 27) {
364 | event.data.editor.abort();
365 | }
366 | }
367 | },
368 |
369 | "date" : {
370 | activateForm : function() {
371 | var that = this,
372 | output = jQuery(document.createElement('form'))
373 | .addClass('form_in_place')
374 | .attr('action', 'javascript:void(0);')
375 | .attr('style', 'display:inline'),
376 | input_elt = jQuery(document.createElement('input'))
377 | .attr('type', 'text')
378 | .attr('name', this.attributeName)
379 | .attr('value', this.sanitizeValue(this.display_value));
380 | if(this.inner_class !== null) {
381 | input_elt.addClass(this.inner_class);
382 | }
383 | output.append(input_elt)
384 |
385 | this.element.html(output);
386 | this.setHtmlAttributes();
387 | this.element.find('input')[0].select();
388 | this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler);
389 | this.element.find("input").bind('keyup', {editor: this}, BestInPlaceEditor.forms.input.keyupHandler);
390 |
391 | this.element.find('input')
392 | .datepicker({
393 | onClose: function() {
394 | that.update();
395 | }
396 | })
397 | .datepicker('show');
398 | },
399 |
400 | getValue : function() {
401 | return this.sanitizeValue(this.element.find("input").val());
402 | },
403 |
404 | submitHandler : function(event) {
405 | event.data.editor.update();
406 | },
407 |
408 | keyupHandler : function(event) {
409 | if (event.keyCode == 27) {
410 | event.data.editor.abort();
411 | }
412 | }
413 | },
414 |
415 | "select" : {
416 | activateForm : function() {
417 | var output = jQuery(document.createElement('form'))
418 | .attr('action', 'javascript:void(0)')
419 | .attr('style', 'display:inline');
420 | selected = '',
421 | oldValue = this.oldValue,
422 | select_elt = jQuery(document.createElement('select')),
423 | currentCollectionValue = this.collectionValue;
424 |
425 | jQuery.each(this.values, function (index, value) {
426 | var option_elt = jQuery(document.createElement('option'))
427 | // .attr('value', value[0])
428 | .val(value[0])
429 | .html(value[1]);
430 | if(value[0] == currentCollectionValue) {
431 | option_elt.attr('selected', 'selected');
432 | }
433 | select_elt.append(option_elt);
434 | });
435 | output.append(select_elt);
436 |
437 | this.element.html(output);
438 | this.setHtmlAttributes();
439 | this.element.find("select").bind('change', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
440 | this.element.find("select").bind('blur', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
441 | this.element.find("select").bind('keyup', {editor: this}, BestInPlaceEditor.forms.select.keyupHandler);
442 | this.element.find("select")[0].focus();
443 | },
444 |
445 | getValue : function() {
446 | return this.sanitizeValue(this.element.find("select").val());
447 | // return this.element.find("select").val();
448 | },
449 |
450 | blurHandler : function(event) {
451 | event.data.editor.update();
452 | },
453 |
454 | keyupHandler : function(event) {
455 | if (event.keyCode == 27) event.data.editor.abort();
456 | }
457 | },
458 |
459 | "checkbox" : {
460 | activateForm : function() {
461 | var newValue = Boolean(this.oldValue.toLowerCase() != this.values[1].toLowerCase());
462 | var output = newValue ? this.values[1] : this.values[0];
463 | this.element.html(output);
464 | this.setHtmlAttributes();
465 | this.update();
466 | },
467 |
468 | getValue : function() {
469 | return Boolean(this.element.html().toLowerCase() == this.values[1].toLowerCase());
470 | }
471 | },
472 |
473 | "textarea" : {
474 | activateForm : function() {
475 | // grab width and height of text
476 | width = this.element.css('width');
477 | height = this.element.css('height');
478 |
479 | // construct form
480 | var output = jQuery(document.createElement('form'))
481 | .attr('action', 'javascript:void(0)')
482 | .attr('style', 'display:inline')
483 | .append(jQuery(document.createElement('textarea'))
484 | .val(this.sanitizeValue(this.display_value)));
485 | if(this.okButton) {
486 | output.append(
487 | jQuery(document.createElement('input'))
488 | .attr('type', 'submit')
489 | .attr('value', this.okButton)
490 | );
491 | }
492 | if(this.cancelButton) {
493 | output.append(
494 | jQuery(document.createElement('input'))
495 | .attr('type', 'button')
496 | .attr('value', this.cancelButton)
497 | )
498 | }
499 |
500 | this.element.html(output);
501 | this.setHtmlAttributes();
502 |
503 | // set width and height of textarea
504 | jQuery(this.element.find("textarea")[0]).css({ 'min-width': width, 'min-height': height });
505 | jQuery(this.element.find("textarea")[0]).elastic();
506 |
507 | this.element.find("textarea")[0].focus();
508 | this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.textarea.submitHandler);
509 | if (this.cancelButton) {
510 | this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.textarea.cancelButtonHandler);
511 | }
512 | this.element.find("textarea").bind('blur', {editor: this}, BestInPlaceEditor.forms.textarea.blurHandler);
513 | this.element.find("textarea").bind('keyup', {editor: this}, BestInPlaceEditor.forms.textarea.keyupHandler);
514 | this.blurTimer = null;
515 | this.userClicked = false;
516 | },
517 |
518 | getValue : function() {
519 | return this.sanitizeValue(this.element.find("textarea").val());
520 | },
521 |
522 | // When buttons are present, use a timer on the blur event to give precedence to clicks
523 | blurHandler : function(event) {
524 | if (event.data.editor.okButton) {
525 | event.data.editor.blurTimer = setTimeout(function () {
526 | if (!event.data.editor.userClicked) {
527 | event.data.editor.abortIfConfirm();
528 | }
529 | }, 500);
530 | } else {
531 | if (event.data.editor.cancelButton) {
532 | event.data.editor.blurTimer = setTimeout(function () {
533 | if (!event.data.editor.userClicked) {
534 | event.data.editor.update();
535 | }
536 | }, 500);
537 | } else {
538 | event.data.editor.update();
539 | }
540 | }
541 | },
542 |
543 | submitHandler : function(event) {
544 | event.data.editor.userClicked = true;
545 | clearTimeout(event.data.editor.blurTimer);
546 | event.data.editor.update();
547 | },
548 |
549 | cancelButtonHandler : function(event) {
550 | event.data.editor.userClicked = true;
551 | clearTimeout(event.data.editor.blurTimer);
552 | event.data.editor.abortIfConfirm();
553 | event.stopPropagation(); // Without this, click isn't handled
554 | },
555 |
556 | keyupHandler : function(event) {
557 | if (event.keyCode == 27) {
558 | event.data.editor.abortIfConfirm();
559 | }
560 | }
561 | }
562 | };
563 |
564 | jQuery.fn.best_in_place = function() {
565 |
566 | function setBestInPlace(element) {
567 | if (!element.data('bestInPlaceEditor')) {
568 | element.data('bestInPlaceEditor', new BestInPlaceEditor(element));
569 | return true;
570 | }
571 | }
572 |
573 | jQuery(this.context).delegate(this.selector, 'click', function () {
574 | var el = jQuery(this);
575 | if (setBestInPlace(el))
576 | el.click();
577 | });
578 |
579 | this.each(function () {
580 | setBestInPlace(jQuery(this));
581 | });
582 |
583 | return this;
584 | };
585 |
586 |
587 |
588 | /**
589 | * @name Elastic
590 | * @descripton Elastic is Jquery plugin that grow and shrink your textareas automaticliy
591 | * @version 1.6.5
592 | * @requires Jquery 1.2.6+
593 | *
594 | * @author Jan Jarfalk
595 | * @author-email jan.jarfalk@unwrongest.com
596 | * @author-website http://www.unwrongest.com
597 | *
598 | * @licens MIT License - http://www.opensource.org/licenses/mit-license.php
599 | */
600 |
601 | (function(jQuery){
602 | jQuery.fn.extend({
603 | elastic: function() {
604 | // We will create a div clone of the textarea
605 | // by copying these attributes from the textarea to the div.
606 | var mimics = [
607 | 'paddingTop',
608 | 'paddingRight',
609 | 'paddingBottom',
610 | 'paddingLeft',
611 | 'fontSize',
612 | 'lineHeight',
613 | 'fontFamily',
614 | 'width',
615 | 'fontWeight'];
616 |
617 | return this.each( function() {
618 |
619 | // Elastic only works on textareas
620 | if ( this.type != 'textarea' ) {
621 | return false;
622 | }
623 |
624 | var $textarea = jQuery(this),
625 | $twin = jQuery('
').css({'position': 'absolute','display':'none','word-wrap':'break-word'}),
626 | lineHeight = parseInt($textarea.css('line-height'),10) || parseInt($textarea.css('font-size'),'10'),
627 | minheight = parseInt($textarea.css('height'),10) || lineHeight*3,
628 | maxheight = parseInt($textarea.css('max-height'),10) || Number.MAX_VALUE,
629 | goalheight = 0,
630 | i = 0;
631 |
632 | // Opera returns max-height of -1 if not set
633 | if (maxheight < 0) { maxheight = Number.MAX_VALUE; }
634 |
635 | // Append the twin to the DOM
636 | // We are going to meassure the height of this, not the textarea.
637 | $twin.appendTo($textarea.parent());
638 |
639 | // Copy the essential styles (mimics) from the textarea to the twin
640 | i = mimics.length;
641 | while(i--){
642 | $twin.css(mimics[i].toString(),$textarea.css(mimics[i].toString()));
643 | }
644 |
645 |
646 | // Sets a given height and overflow state on the textarea
647 | function setHeightAndOverflow(height, overflow){
648 | curratedHeight = Math.floor(parseInt(height,10));
649 | if($textarea.height() != curratedHeight){
650 | $textarea.css({'height': curratedHeight + 'px','overflow':overflow});
651 |
652 | }
653 | }
654 |
655 |
656 | // This function will update the height of the textarea if necessary
657 | function update() {
658 |
659 | // Get curated content from the textarea.
660 | var textareaContent = $textarea.val().replace(/&/g,'&').replace(/ /g, ' ').replace(/<|>/g, '>').replace(/\n/g, '
');
661 |
662 | // Compare curated content with curated twin.
663 | var twinContent = $twin.html().replace(/
/ig,'
');
664 |
665 | if(textareaContent+' ' != twinContent){
666 |
667 | // Add an extra white space so new rows are added when you are at the end of a row.
668 | $twin.html(textareaContent+' ');
669 |
670 | // Change textarea height if twin plus the height of one line differs more than 3 pixel from textarea height
671 | if(Math.abs($twin.height() + lineHeight - $textarea.height()) > 3){
672 |
673 | var goalheight = $twin.height()+lineHeight;
674 | if(goalheight >= maxheight) {
675 | setHeightAndOverflow(maxheight,'auto');
676 | } else if(goalheight <= minheight) {
677 | setHeightAndOverflow(minheight,'hidden');
678 | } else {
679 | setHeightAndOverflow(goalheight,'hidden');
680 | }
681 |
682 | }
683 |
684 | }
685 |
686 | }
687 |
688 | // Hide scrollbars
689 | $textarea.css({'overflow':'hidden'});
690 |
691 | // Update textarea size on keyup, change, cut and paste
692 | $textarea.bind('keyup change cut paste', function(){
693 | update();
694 | });
695 |
696 | // Compact textarea on blur
697 | // Lets animate this....
698 | $textarea.bind('blur',function(){
699 | if($twin.height() < maxheight){
700 | if($twin.height() > minheight) {
701 | $textarea.height($twin.height());
702 | } else {
703 | $textarea.height(minheight);
704 | }
705 | }
706 | });
707 |
708 | // And this line is to catch the browser paste event
709 | $textarea.live('input paste',function(e){ setTimeout( update, 250); });
710 |
711 | // Run update once when elastic is initialized
712 | update();
713 |
714 | });
715 |
716 | }
717 | });
718 | })(jQuery);
719 |
--------------------------------------------------------------------------------
/priv/static/js/app.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["node_modules/phoenix/priv/static/phoenix.js","node_modules/phoenix_html/priv/static/phoenix_html.js","socket.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACvgCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;AChBA,IAAI,SAAS,oBAAW,SAAX,EAAsB,EAAC,QAAQ,EAAC,OAAO,OAAO,SAAP,EAAhB,EAAvB,CAAT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CJ,OAAO,OAAP;;;AAGA,IAAI,UAAU,OAAO,OAAP,CAAe,gBAAf,EAAiC,EAAjC,CAAV;AACJ,QAAQ,IAAR,GACG,OADH,CACW,IADX,EACiB,gBAAQ;AAAE,UAAQ,GAAR,CAAY,qBAAZ,EAAmC,IAAnC,EAAF;CAAR,CADjB,CAEG,OAFH,CAEW,OAFX,EAEoB,gBAAQ;AAAE,UAAQ,GAAR,CAAY,gBAAZ,EAA8B,IAA9B,EAAF;CAAR,CAFpB;;kBAIe","file":"priv/static/js/app.js","sourcesContent":["require.register('phoenix', function(exports,req,module){\n var require = __makeRequire((function(n) { return req(n.replace('./', 'phoenix//priv/static/')); }), {});\n (function(exports,require,module) {\n (function(exports){\n\"use strict\";\n\nvar _typeof = typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol ? \"symbol\" : typeof obj; };\n\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\n// Phoenix Channels JavaScript client\n//\n// ## Socket Connection\n//\n// A single connection is established to the server and\n// channels are mulitplexed over the connection.\n// Connect to the server using the `Socket` class:\n//\n// let socket = new Socket(\"/ws\", {params: {userToken: \"123\"}})\n// socket.connect()\n//\n// The `Socket` constructor takes the mount point of the socket,\n// the authentication params, as well as options that can be found in\n// the Socket docs, such as configuring the `LongPoll` transport, and\n// heartbeat.\n//\n// ## Channels\n//\n// Channels are isolated, concurrent processes on the server that\n// subscribe to topics and broker events between the client and server.\n// To join a channel, you must provide the topic, and channel params for\n// authorization. Here's an example chat room example where `\"new_msg\"`\n// events are listened for, messages are pushed to the server, and\n// the channel is joined with ok/error/timeout matches:\n//\n// let channel = socket.channel(\"rooms:123\", {token: roomToken})\n// channel.on(\"new_msg\", msg => console.log(\"Got message\", msg) )\n// $input.onEnter( e => {\n// channel.push(\"new_msg\", {body: e.target.val}, 10000)\n// .receive(\"ok\", (msg) => console.log(\"created message\", msg) )\n// .receive(\"error\", (reasons) => console.log(\"create failed\", reasons) )\n// .receive(\"timeout\", () => console.log(\"Networking issue...\") )\n// })\n// channel.join()\n// .receive(\"ok\", ({messages}) => console.log(\"catching up\", messages) )\n// .receive(\"error\", ({reason}) => console.log(\"failed join\", reason) )\n// .receive(\"timeout\", () => console.log(\"Networking issue. Still waiting...\") )\n//\n//\n// ## Joining\n//\n// Creating a channel with `socket.channel(topic, params)`, binds the params to\n// `channel.params`, which are sent up on `channel.join()`.\n// Subsequent rejoins will send up the modified params for\n// updating authorization params, or passing up last_message_id information.\n// Successful joins receive an \"ok\" status, while unsuccessful joins\n// receive \"error\".\n//\n//\n// ## Pushing Messages\n//\n// From the previous example, we can see that pushing messages to the server\n// can be done with `channel.push(eventName, payload)` and we can optionally\n// receive responses from the push. Additionally, we can use\n// `receive(\"timeout\", callback)` to abort waiting for our other `receive` hooks\n// and take action after some period of waiting. The default timeout is 5000ms.\n//\n//\n// ## Socket Hooks\n//\n// Lifecycle events of the multiplexed connection can be hooked into via\n// `socket.onError()` and `socket.onClose()` events, ie:\n//\n// socket.onError( () => console.log(\"there was an error with the connection!\") )\n// socket.onClose( () => console.log(\"the connection dropped\") )\n//\n//\n// ## Channel Hooks\n//\n// For each joined channel, you can bind to `onError` and `onClose` events\n// to monitor the channel lifecycle, ie:\n//\n// channel.onError( () => console.log(\"there was an error!\") )\n// channel.onClose( () => console.log(\"the channel has gone away gracefully\") )\n//\n// ### onError hooks\n//\n// `onError` hooks are invoked if the socket connection drops, or the channel\n// crashes on the server. In either case, a channel rejoin is attemtped\n// automatically in an exponential backoff manner.\n//\n// ### onClose hooks\n//\n// `onClose` hooks are invoked only in two cases. 1) the channel explicitly\n// closed on the server, or 2). The client explicitly closed, by calling\n// `channel.leave()`\n//\n\nvar VSN = \"1.0.0\";\nvar SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 };\nvar DEFAULT_TIMEOUT = 10000;\nvar CHANNEL_STATES = {\n closed: \"closed\",\n errored: \"errored\",\n joined: \"joined\",\n joining: \"joining\"\n};\nvar CHANNEL_EVENTS = {\n close: \"phx_close\",\n error: \"phx_error\",\n join: \"phx_join\",\n reply: \"phx_reply\",\n leave: \"phx_leave\"\n};\nvar TRANSPORTS = {\n longpoll: \"longpoll\",\n websocket: \"websocket\"\n};\n\nvar Push = function () {\n\n // Initializes the Push\n //\n // channel - The Channel\n // event - The event, for example `\"phx_join\"`\n // payload - The payload, for example `{user_id: 123}`\n // timeout - The push timeout in milliseconds\n //\n\n function Push(channel, event, payload, timeout) {\n _classCallCheck(this, Push);\n\n this.channel = channel;\n this.event = event;\n this.payload = payload || {};\n this.receivedResp = null;\n this.timeout = timeout;\n this.timeoutTimer = null;\n this.recHooks = [];\n this.sent = false;\n }\n\n _createClass(Push, [{\n key: \"resend\",\n value: function resend(timeout) {\n this.timeout = timeout;\n this.cancelRefEvent();\n this.ref = null;\n this.refEvent = null;\n this.receivedResp = null;\n this.sent = false;\n this.send();\n }\n }, {\n key: \"send\",\n value: function send() {\n if (this.hasReceived(\"timeout\")) {\n return;\n }\n this.startTimeout();\n this.sent = true;\n this.channel.socket.push({\n topic: this.channel.topic,\n event: this.event,\n payload: this.payload,\n ref: this.ref\n });\n }\n }, {\n key: \"receive\",\n value: function receive(status, callback) {\n if (this.hasReceived(status)) {\n callback(this.receivedResp.response);\n }\n\n this.recHooks.push({ status: status, callback: callback });\n return this;\n }\n\n // private\n\n }, {\n key: \"matchReceive\",\n value: function matchReceive(_ref) {\n var status = _ref.status;\n var response = _ref.response;\n var ref = _ref.ref;\n\n this.recHooks.filter(function (h) {\n return h.status === status;\n }).forEach(function (h) {\n return h.callback(response);\n });\n }\n }, {\n key: \"cancelRefEvent\",\n value: function cancelRefEvent() {\n if (!this.refEvent) {\n return;\n }\n this.channel.off(this.refEvent);\n }\n }, {\n key: \"cancelTimeout\",\n value: function cancelTimeout() {\n clearTimeout(this.timeoutTimer);\n this.timeoutTimer = null;\n }\n }, {\n key: \"startTimeout\",\n value: function startTimeout() {\n var _this = this;\n\n if (this.timeoutTimer) {\n return;\n }\n this.ref = this.channel.socket.makeRef();\n this.refEvent = this.channel.replyEventName(this.ref);\n\n this.channel.on(this.refEvent, function (payload) {\n _this.cancelRefEvent();\n _this.cancelTimeout();\n _this.receivedResp = payload;\n _this.matchReceive(payload);\n });\n\n this.timeoutTimer = setTimeout(function () {\n _this.trigger(\"timeout\", {});\n }, this.timeout);\n }\n }, {\n key: \"hasReceived\",\n value: function hasReceived(status) {\n return this.receivedResp && this.receivedResp.status === status;\n }\n }, {\n key: \"trigger\",\n value: function trigger(status, response) {\n this.channel.trigger(this.refEvent, { status: status, response: response });\n }\n }]);\n\n return Push;\n}();\n\nvar Channel = exports.Channel = function () {\n function Channel(topic, params, socket) {\n var _this2 = this;\n\n _classCallCheck(this, Channel);\n\n this.state = CHANNEL_STATES.closed;\n this.topic = topic;\n this.params = params || {};\n this.socket = socket;\n this.bindings = [];\n this.timeout = this.socket.timeout;\n this.joinedOnce = false;\n this.joinPush = new Push(this, CHANNEL_EVENTS.join, this.params, this.timeout);\n this.pushBuffer = [];\n this.rejoinTimer = new Timer(function () {\n return _this2.rejoinUntilConnected();\n }, this.socket.reconnectAfterMs);\n this.joinPush.receive(\"ok\", function () {\n _this2.state = CHANNEL_STATES.joined;\n _this2.rejoinTimer.reset();\n _this2.pushBuffer.forEach(function (pushEvent) {\n return pushEvent.send();\n });\n _this2.pushBuffer = [];\n });\n this.onClose(function () {\n _this2.socket.log(\"channel\", \"close \" + _this2.topic);\n _this2.state = CHANNEL_STATES.closed;\n _this2.socket.remove(_this2);\n });\n this.onError(function (reason) {\n _this2.socket.log(\"channel\", \"error \" + _this2.topic, reason);\n _this2.state = CHANNEL_STATES.errored;\n _this2.rejoinTimer.scheduleTimeout();\n });\n this.joinPush.receive(\"timeout\", function () {\n if (_this2.state !== CHANNEL_STATES.joining) {\n return;\n }\n\n _this2.socket.log(\"channel\", \"timeout \" + _this2.topic, _this2.joinPush.timeout);\n _this2.state = CHANNEL_STATES.errored;\n _this2.rejoinTimer.scheduleTimeout();\n });\n this.on(CHANNEL_EVENTS.reply, function (payload, ref) {\n _this2.trigger(_this2.replyEventName(ref), payload);\n });\n }\n\n _createClass(Channel, [{\n key: \"rejoinUntilConnected\",\n value: function rejoinUntilConnected() {\n this.rejoinTimer.scheduleTimeout();\n if (this.socket.isConnected()) {\n this.rejoin();\n }\n }\n }, {\n key: \"join\",\n value: function join() {\n var timeout = arguments.length <= 0 || arguments[0] === undefined ? this.timeout : arguments[0];\n\n if (this.joinedOnce) {\n throw \"tried to join multiple times. 'join' can only be called a single time per channel instance\";\n } else {\n this.joinedOnce = true;\n }\n this.rejoin(timeout);\n return this.joinPush;\n }\n }, {\n key: \"onClose\",\n value: function onClose(callback) {\n this.on(CHANNEL_EVENTS.close, callback);\n }\n }, {\n key: \"onError\",\n value: function onError(callback) {\n this.on(CHANNEL_EVENTS.error, function (reason) {\n return callback(reason);\n });\n }\n }, {\n key: \"on\",\n value: function on(event, callback) {\n this.bindings.push({ event: event, callback: callback });\n }\n }, {\n key: \"off\",\n value: function off(event) {\n this.bindings = this.bindings.filter(function (bind) {\n return bind.event !== event;\n });\n }\n }, {\n key: \"canPush\",\n value: function canPush() {\n return this.socket.isConnected() && this.state === CHANNEL_STATES.joined;\n }\n }, {\n key: \"push\",\n value: function push(event, payload) {\n var timeout = arguments.length <= 2 || arguments[2] === undefined ? this.timeout : arguments[2];\n\n if (!this.joinedOnce) {\n throw \"tried to push '\" + event + \"' to '\" + this.topic + \"' before joining. Use channel.join() before pushing events\";\n }\n var pushEvent = new Push(this, event, payload, timeout);\n if (this.canPush()) {\n pushEvent.send();\n } else {\n pushEvent.startTimeout();\n this.pushBuffer.push(pushEvent);\n }\n\n return pushEvent;\n }\n\n // Leaves the channel\n //\n // Unsubscribes from server events, and\n // instructs channel to terminate on server\n //\n // Triggers onClose() hooks\n //\n // To receive leave acknowledgements, use the a `receive`\n // hook to bind to the server ack, ie:\n //\n // channel.leave().receive(\"ok\", () => alert(\"left!\") )\n //\n\n }, {\n key: \"leave\",\n value: function leave() {\n var _this3 = this;\n\n var timeout = arguments.length <= 0 || arguments[0] === undefined ? this.timeout : arguments[0];\n\n var onClose = function onClose() {\n _this3.socket.log(\"channel\", \"leave \" + _this3.topic);\n _this3.trigger(CHANNEL_EVENTS.close, \"leave\");\n };\n var leavePush = new Push(this, CHANNEL_EVENTS.leave, {}, timeout);\n leavePush.receive(\"ok\", function () {\n return onClose();\n }).receive(\"timeout\", function () {\n return onClose();\n });\n leavePush.send();\n if (!this.canPush()) {\n leavePush.trigger(\"ok\", {});\n }\n\n return leavePush;\n }\n\n // Overridable message hook\n //\n // Receives all events for specialized message handling\n\n }, {\n key: \"onMessage\",\n value: function onMessage(event, payload, ref) {}\n\n // private\n\n }, {\n key: \"isMember\",\n value: function isMember(topic) {\n return this.topic === topic;\n }\n }, {\n key: \"sendJoin\",\n value: function sendJoin(timeout) {\n this.state = CHANNEL_STATES.joining;\n this.joinPush.resend(timeout);\n }\n }, {\n key: \"rejoin\",\n value: function rejoin() {\n var timeout = arguments.length <= 0 || arguments[0] === undefined ? this.timeout : arguments[0];\n this.sendJoin(timeout);\n }\n }, {\n key: \"trigger\",\n value: function trigger(triggerEvent, payload, ref) {\n this.onMessage(triggerEvent, payload, ref);\n this.bindings.filter(function (bind) {\n return bind.event === triggerEvent;\n }).map(function (bind) {\n return bind.callback(payload, ref);\n });\n }\n }, {\n key: \"replyEventName\",\n value: function replyEventName(ref) {\n return \"chan_reply_\" + ref;\n }\n }]);\n\n return Channel;\n}();\n\nvar Socket = exports.Socket = function () {\n\n // Initializes the Socket\n //\n // endPoint - The string WebSocket endpoint, ie, \"ws://example.com/ws\",\n // \"wss://example.com\"\n // \"/ws\" (inherited host & protocol)\n // opts - Optional configuration\n // transport - The Websocket Transport, for example WebSocket or Phoenix.LongPoll.\n // Defaults to WebSocket with automatic LongPoll fallback.\n // timeout - The default timeout in milliseconds to trigger push timeouts.\n // Defaults `DEFAULT_TIMEOUT`\n // heartbeatIntervalMs - The millisec interval to send a heartbeat message\n // reconnectAfterMs - The optional function that returns the millsec\n // reconnect interval. Defaults to stepped backoff of:\n //\n // function(tries){\n // return [1000, 5000, 10000][tries - 1] || 10000\n // }\n //\n // logger - The optional function for specialized logging, ie:\n // `logger: (kind, msg, data) => { console.log(`${kind}: ${msg}`, data) }\n //\n // longpollerTimeout - The maximum timeout of a long poll AJAX request.\n // Defaults to 20s (double the server long poll timer).\n //\n // params - The optional params to pass when connecting\n //\n // For IE8 support use an ES5-shim (https://github.com/es-shims/es5-shim)\n //\n\n function Socket(endPoint) {\n var _this4 = this;\n\n var opts = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];\n\n _classCallCheck(this, Socket);\n\n this.stateChangeCallbacks = { open: [], close: [], error: [], message: [] };\n this.channels = [];\n this.sendBuffer = [];\n this.ref = 0;\n this.timeout = opts.timeout || DEFAULT_TIMEOUT;\n this.transport = opts.transport || window.WebSocket || LongPoll;\n this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 30000;\n this.reconnectAfterMs = opts.reconnectAfterMs || function (tries) {\n return [1000, 2000, 5000, 10000][tries - 1] || 10000;\n };\n this.logger = opts.logger || function () {}; // noop\n this.longpollerTimeout = opts.longpollerTimeout || 20000;\n this.params = opts.params || {};\n this.endPoint = endPoint + \"/\" + TRANSPORTS.websocket;\n this.reconnectTimer = new Timer(function () {\n _this4.disconnect(function () {\n return _this4.connect();\n });\n }, this.reconnectAfterMs);\n }\n\n _createClass(Socket, [{\n key: \"protocol\",\n value: function protocol() {\n return location.protocol.match(/^https/) ? \"wss\" : \"ws\";\n }\n }, {\n key: \"endPointURL\",\n value: function endPointURL() {\n var uri = Ajax.appendParams(Ajax.appendParams(this.endPoint, this.params), { vsn: VSN });\n if (uri.charAt(0) !== \"/\") {\n return uri;\n }\n if (uri.charAt(1) === \"/\") {\n return this.protocol() + \":\" + uri;\n }\n\n return this.protocol() + \"://\" + location.host + uri;\n }\n }, {\n key: \"disconnect\",\n value: function disconnect(callback, code, reason) {\n if (this.conn) {\n this.conn.onclose = function () {}; // noop\n if (code) {\n this.conn.close(code, reason || \"\");\n } else {\n this.conn.close();\n }\n this.conn = null;\n }\n callback && callback();\n }\n\n // params - The params to send when connecting, for example `{user_id: userToken}`\n\n }, {\n key: \"connect\",\n value: function connect(params) {\n var _this5 = this;\n\n if (params) {\n console && console.log(\"passing params to connect is deprecated. Instead pass :params to the Socket constructor\");\n this.params = params;\n }\n if (this.conn) {\n return;\n }\n\n this.conn = new this.transport(this.endPointURL());\n this.conn.timeout = this.longpollerTimeout;\n this.conn.onopen = function () {\n return _this5.onConnOpen();\n };\n this.conn.onerror = function (error) {\n return _this5.onConnError(error);\n };\n this.conn.onmessage = function (event) {\n return _this5.onConnMessage(event);\n };\n this.conn.onclose = function (event) {\n return _this5.onConnClose(event);\n };\n }\n\n // Logs the message. Override `this.logger` for specialized logging. noops by default\n\n }, {\n key: \"log\",\n value: function log(kind, msg, data) {\n this.logger(kind, msg, data);\n }\n\n // Registers callbacks for connection state change events\n //\n // Examples\n //\n // socket.onError(function(error){ alert(\"An error occurred\") })\n //\n\n }, {\n key: \"onOpen\",\n value: function onOpen(callback) {\n this.stateChangeCallbacks.open.push(callback);\n }\n }, {\n key: \"onClose\",\n value: function onClose(callback) {\n this.stateChangeCallbacks.close.push(callback);\n }\n }, {\n key: \"onError\",\n value: function onError(callback) {\n this.stateChangeCallbacks.error.push(callback);\n }\n }, {\n key: \"onMessage\",\n value: function onMessage(callback) {\n this.stateChangeCallbacks.message.push(callback);\n }\n }, {\n key: \"onConnOpen\",\n value: function onConnOpen() {\n var _this6 = this;\n\n this.log(\"transport\", \"connected to \" + this.endPointURL(), this.transport.prototype);\n this.flushSendBuffer();\n this.reconnectTimer.reset();\n if (!this.conn.skipHeartbeat) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = setInterval(function () {\n return _this6.sendHeartbeat();\n }, this.heartbeatIntervalMs);\n }\n this.stateChangeCallbacks.open.forEach(function (callback) {\n return callback();\n });\n }\n }, {\n key: \"onConnClose\",\n value: function onConnClose(event) {\n this.log(\"transport\", \"close\", event);\n this.triggerChanError();\n clearInterval(this.heartbeatTimer);\n this.reconnectTimer.scheduleTimeout();\n this.stateChangeCallbacks.close.forEach(function (callback) {\n return callback(event);\n });\n }\n }, {\n key: \"onConnError\",\n value: function onConnError(error) {\n this.log(\"transport\", error);\n this.triggerChanError();\n this.stateChangeCallbacks.error.forEach(function (callback) {\n return callback(error);\n });\n }\n }, {\n key: \"triggerChanError\",\n value: function triggerChanError() {\n this.channels.forEach(function (channel) {\n return channel.trigger(CHANNEL_EVENTS.error);\n });\n }\n }, {\n key: \"connectionState\",\n value: function connectionState() {\n switch (this.conn && this.conn.readyState) {\n case SOCKET_STATES.connecting:\n return \"connecting\";\n case SOCKET_STATES.open:\n return \"open\";\n case SOCKET_STATES.closing:\n return \"closing\";\n default:\n return \"closed\";\n }\n }\n }, {\n key: \"isConnected\",\n value: function isConnected() {\n return this.connectionState() === \"open\";\n }\n }, {\n key: \"remove\",\n value: function remove(channel) {\n this.channels = this.channels.filter(function (c) {\n return !c.isMember(channel.topic);\n });\n }\n }, {\n key: \"channel\",\n value: function channel(topic) {\n var chanParams = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];\n\n var chan = new Channel(topic, chanParams, this);\n this.channels.push(chan);\n return chan;\n }\n }, {\n key: \"push\",\n value: function push(data) {\n var _this7 = this;\n\n var topic = data.topic;\n var event = data.event;\n var payload = data.payload;\n var ref = data.ref;\n\n var callback = function callback() {\n return _this7.conn.send(JSON.stringify(data));\n };\n this.log(\"push\", topic + \" \" + event + \" (\" + ref + \")\", payload);\n if (this.isConnected()) {\n callback();\n } else {\n this.sendBuffer.push(callback);\n }\n }\n\n // Return the next message ref, accounting for overflows\n\n }, {\n key: \"makeRef\",\n value: function makeRef() {\n var newRef = this.ref + 1;\n if (newRef === this.ref) {\n this.ref = 0;\n } else {\n this.ref = newRef;\n }\n\n return this.ref.toString();\n }\n }, {\n key: \"sendHeartbeat\",\n value: function sendHeartbeat() {\n if (!this.isConnected()) {\n return;\n }\n this.push({ topic: \"phoenix\", event: \"heartbeat\", payload: {}, ref: this.makeRef() });\n }\n }, {\n key: \"flushSendBuffer\",\n value: function flushSendBuffer() {\n if (this.isConnected() && this.sendBuffer.length > 0) {\n this.sendBuffer.forEach(function (callback) {\n return callback();\n });\n this.sendBuffer = [];\n }\n }\n }, {\n key: \"onConnMessage\",\n value: function onConnMessage(rawMessage) {\n var msg = JSON.parse(rawMessage.data);\n var topic = msg.topic;\n var event = msg.event;\n var payload = msg.payload;\n var ref = msg.ref;\n\n this.log(\"receive\", (payload.status || \"\") + \" \" + topic + \" \" + event + \" \" + (ref && \"(\" + ref + \")\" || \"\"), payload);\n this.channels.filter(function (channel) {\n return channel.isMember(topic);\n }).forEach(function (channel) {\n return channel.trigger(event, payload, ref);\n });\n this.stateChangeCallbacks.message.forEach(function (callback) {\n return callback(msg);\n });\n }\n }]);\n\n return Socket;\n}();\n\nvar LongPoll = exports.LongPoll = function () {\n function LongPoll(endPoint) {\n _classCallCheck(this, LongPoll);\n\n this.endPoint = null;\n this.token = null;\n this.skipHeartbeat = true;\n this.onopen = function () {}; // noop\n this.onerror = function () {}; // noop\n this.onmessage = function () {}; // noop\n this.onclose = function () {}; // noop\n this.pollEndpoint = this.normalizeEndpoint(endPoint);\n this.readyState = SOCKET_STATES.connecting;\n\n this.poll();\n }\n\n _createClass(LongPoll, [{\n key: \"normalizeEndpoint\",\n value: function normalizeEndpoint(endPoint) {\n return endPoint.replace(\"ws://\", \"http://\").replace(\"wss://\", \"https://\").replace(new RegExp(\"(.*)\\/\" + TRANSPORTS.websocket), \"$1/\" + TRANSPORTS.longpoll);\n }\n }, {\n key: \"endpointURL\",\n value: function endpointURL() {\n return Ajax.appendParams(this.pollEndpoint, { token: this.token });\n }\n }, {\n key: \"closeAndRetry\",\n value: function closeAndRetry() {\n this.close();\n this.readyState = SOCKET_STATES.connecting;\n }\n }, {\n key: \"ontimeout\",\n value: function ontimeout() {\n this.onerror(\"timeout\");\n this.closeAndRetry();\n }\n }, {\n key: \"poll\",\n value: function poll() {\n var _this8 = this;\n\n if (!(this.readyState === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting)) {\n return;\n }\n\n Ajax.request(\"GET\", this.endpointURL(), \"application/json\", null, this.timeout, this.ontimeout.bind(this), function (resp) {\n if (resp) {\n var status = resp.status;\n var token = resp.token;\n var messages = resp.messages;\n\n _this8.token = token;\n } else {\n var status = 0;\n }\n\n switch (status) {\n case 200:\n messages.forEach(function (msg) {\n return _this8.onmessage({ data: JSON.stringify(msg) });\n });\n _this8.poll();\n break;\n case 204:\n _this8.poll();\n break;\n case 410:\n _this8.readyState = SOCKET_STATES.open;\n _this8.onopen();\n _this8.poll();\n break;\n case 0:\n case 500:\n _this8.onerror();\n _this8.closeAndRetry();\n break;\n default:\n throw \"unhandled poll status \" + status;\n }\n });\n }\n }, {\n key: \"send\",\n value: function send(body) {\n var _this9 = this;\n\n Ajax.request(\"POST\", this.endpointURL(), \"application/json\", body, this.timeout, this.onerror.bind(this, \"timeout\"), function (resp) {\n if (!resp || resp.status !== 200) {\n _this9.onerror(status);\n _this9.closeAndRetry();\n }\n });\n }\n }, {\n key: \"close\",\n value: function close(code, reason) {\n this.readyState = SOCKET_STATES.closed;\n this.onclose();\n }\n }]);\n\n return LongPoll;\n}();\n\nvar Ajax = exports.Ajax = function () {\n function Ajax() {\n _classCallCheck(this, Ajax);\n }\n\n _createClass(Ajax, null, [{\n key: \"request\",\n value: function request(method, endPoint, accept, body, timeout, ontimeout, callback) {\n if (window.XDomainRequest) {\n var req = new XDomainRequest(); // IE8, IE9\n this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback);\n } else {\n var req = window.XMLHttpRequest ? new XMLHttpRequest() : // IE7+, Firefox, Chrome, Opera, Safari\n new ActiveXObject(\"Microsoft.XMLHTTP\"); // IE6, IE5\n this.xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback);\n }\n }\n }, {\n key: \"xdomainRequest\",\n value: function xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback) {\n var _this10 = this;\n\n req.timeout = timeout;\n req.open(method, endPoint);\n req.onload = function () {\n var response = _this10.parseJSON(req.responseText);\n callback && callback(response);\n };\n if (ontimeout) {\n req.ontimeout = ontimeout;\n }\n\n // Work around bug in IE9 that requires an attached onprogress handler\n req.onprogress = function () {};\n\n req.send(body);\n }\n }, {\n key: \"xhrRequest\",\n value: function xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback) {\n var _this11 = this;\n\n req.timeout = timeout;\n req.open(method, endPoint, true);\n req.setRequestHeader(\"Content-Type\", accept);\n req.onerror = function () {\n callback && callback(null);\n };\n req.onreadystatechange = function () {\n if (req.readyState === _this11.states.complete && callback) {\n var response = _this11.parseJSON(req.responseText);\n callback(response);\n }\n };\n if (ontimeout) {\n req.ontimeout = ontimeout;\n }\n\n req.send(body);\n }\n }, {\n key: \"parseJSON\",\n value: function parseJSON(resp) {\n return resp && resp !== \"\" ? JSON.parse(resp) : null;\n }\n }, {\n key: \"serialize\",\n value: function serialize(obj, parentKey) {\n var queryStr = [];\n for (var key in obj) {\n if (!obj.hasOwnProperty(key)) {\n continue;\n }\n var paramKey = parentKey ? parentKey + \"[\" + key + \"]\" : key;\n var paramVal = obj[key];\n if ((typeof paramVal === \"undefined\" ? \"undefined\" : _typeof(paramVal)) === \"object\") {\n queryStr.push(this.serialize(paramVal, paramKey));\n } else {\n queryStr.push(encodeURIComponent(paramKey) + \"=\" + encodeURIComponent(paramVal));\n }\n }\n return queryStr.join(\"&\");\n }\n }, {\n key: \"appendParams\",\n value: function appendParams(url, params) {\n if (Object.keys(params).length === 0) {\n return url;\n }\n\n var prefix = url.match(/\\?/) ? \"&\" : \"?\";\n return \"\" + url + prefix + this.serialize(params);\n }\n }]);\n\n return Ajax;\n}();\n\nAjax.states = { complete: 4 };\n\n// Creates a timer that accepts a `timerCalc` function to perform\n// calculated timeout retries, such as exponential backoff.\n//\n// ## Examples\n//\n// let reconnectTimer = new Timer(() => this.connect(), function(tries){\n// return [1000, 5000, 10000][tries - 1] || 10000\n// })\n// reconnectTimer.scheduleTimeout() // fires after 1000\n// reconnectTimer.scheduleTimeout() // fires after 5000\n// reconnectTimer.reset()\n// reconnectTimer.scheduleTimeout() // fires after 1000\n//\n\nvar Timer = function () {\n function Timer(callback, timerCalc) {\n _classCallCheck(this, Timer);\n\n this.callback = callback;\n this.timerCalc = timerCalc;\n this.timer = null;\n this.tries = 0;\n }\n\n _createClass(Timer, [{\n key: \"reset\",\n value: function reset() {\n this.tries = 0;\n clearTimeout(this.timer);\n }\n\n // Cancels any previous scheduleTimeout and schedules callback\n\n }, {\n key: \"scheduleTimeout\",\n value: function scheduleTimeout() {\n var _this12 = this;\n\n clearTimeout(this.timer);\n\n this.timer = setTimeout(function () {\n _this12.tries = _this12.tries + 1;\n _this12.callback();\n }, this.timerCalc(this.tries + 1));\n }\n }]);\n\n return Timer;\n}();\n\n\n})(typeof(exports) === \"undefined\" ? window.Phoenix = window.Phoenix || {} : exports);\n\n })(exports,require,module);\n });","require.register('phoenix_html', function(exports,req,module){\n var require = __makeRequire((function(n) { return req(n.replace('./', 'phoenix_html//priv/static/')); }), {});\n (function(exports,require,module) {\n 'use strict';\n\n// Although ^=parent is not technically correct,\n// we need to use it in order to get IE8 support.\nvar elements = document.querySelectorAll('[data-submit^=parent]');\nvar len = elements.length;\n\nfor (var i = 0; i < len; ++i) {\n elements[i].addEventListener('click', function (event) {\n var message = this.getAttribute(\"data-confirm\");\n if (message === null || confirm(message)) {\n this.parentNode.submit();\n };\n event.preventDefault();\n return false;\n }, false);\n}\n\n;\n })(exports,require,module);\n });","// NOTE: The contents of this file will only be executed if\n// you uncomment its entry in \"web/static/js/app.js\".\n\n// To use Phoenix channels, the first step is to import Socket\n// and connect at the socket path in \"lib/my_app/endpoint.ex\":\nimport {Socket} from \"phoenix\"\n\nlet socket = new Socket(\"/socket\", {params: {token: window.userToken}})\n\n// When you connect, you'll often need to authenticate the client.\n// For example, imagine you have an authentication plug, `MyAuth`,\n// which authenticates the session and assigns a `:current_user`.\n// If the current user exists you can assign the user's token in\n// the connection for use in the layout.\n//\n// In your \"web/router.ex\":\n//\n// pipeline :browser do\n// ...\n// plug MyAuth\n// plug :put_user_token\n// end\n//\n// defp put_user_token(conn, _) do\n// if current_user = conn.assigns[:current_user] do\n// token = Phoenix.Token.sign(conn, \"user socket\", current_user.id)\n// assign(conn, :user_token, token)\n// else\n// conn\n// end\n// end\n//\n// Now you need to pass this token to JavaScript. You can do so\n// inside a script tag in \"web/templates/layout/app.html.eex\":\n//\n// \n//\n// You will need to verify the user token in the \"connect/2\" function\n// in \"web/channels/user_socket.ex\":\n//\n// def connect(%{\"token\" => token}, socket) do\n// # max_age: 1209600 is equivalent to two weeks in seconds\n// case Phoenix.Token.verify(socket, \"user socket\", token, max_age: 1209600) do\n// {:ok, user_id} ->\n// {:ok, assign(socket, :user, user_id)}\n// {:error, reason} ->\n// :error\n// end\n// end\n//\n// Finally, pass the token on connect as below. Or remove it\n// from connect if you don't care about authentication.\n\nsocket.connect()\n\n// Now that you are connected, you can join channels with a topic:\nlet channel = socket.channel(\"topic:subtopic\", {})\nchannel.join()\n .receive(\"ok\", resp => { console.log(\"Joined successfully\", resp) })\n .receive(\"error\", resp => { console.log(\"Unable to join\", resp) })\n\nexport default socket\n"]}
--------------------------------------------------------------------------------
/priv/static/js/app.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | var globals = typeof window === 'undefined' ? global : window;
5 | if (typeof globals.require === 'function') return;
6 |
7 | var modules = {};
8 | var cache = {};
9 | var aliases = {};
10 | var has = ({}).hasOwnProperty;
11 |
12 | var endsWith = function(str, suffix) {
13 | return str.indexOf(suffix, str.length - suffix.length) !== -1;
14 | };
15 |
16 | var _cmp = 'components/';
17 | var unalias = function(alias, loaderPath) {
18 | var start = 0;
19 | if (loaderPath) {
20 | if (loaderPath.indexOf(_cmp) === 0) {
21 | start = _cmp.length;
22 | }
23 | if (loaderPath.indexOf('/', start) > 0) {
24 | loaderPath = loaderPath.substring(start, loaderPath.indexOf('/', start));
25 | }
26 | }
27 | var result = aliases[alias + '/index.js'] || aliases[loaderPath + '/deps/' + alias + '/index.js'];
28 | if (result) {
29 | return _cmp + result.substring(0, result.length - '.js'.length);
30 | }
31 | return alias;
32 | };
33 |
34 | var _reg = /^\.\.?(\/|$)/;
35 | var expand = function(root, name) {
36 | var results = [], part;
37 | var parts = (_reg.test(name) ? root + '/' + name : name).split('/');
38 | for (var i = 0, length = parts.length; i < length; i++) {
39 | part = parts[i];
40 | if (part === '..') {
41 | results.pop();
42 | } else if (part !== '.' && part !== '') {
43 | results.push(part);
44 | }
45 | }
46 | return results.join('/');
47 | };
48 |
49 | var dirname = function(path) {
50 | return path.split('/').slice(0, -1).join('/');
51 | };
52 |
53 | var localRequire = function(path) {
54 | return function expanded(name) {
55 | var absolute = expand(dirname(path), name);
56 | return globals.require(absolute, path);
57 | };
58 | };
59 |
60 | var initModule = function(name, definition) {
61 | var module = {id: name, exports: {}};
62 | cache[name] = module;
63 | definition(module.exports, localRequire(name), module);
64 | return module.exports;
65 | };
66 |
67 | var require = function(name, loaderPath) {
68 | var path = expand(name, '.');
69 | if (loaderPath == null) loaderPath = '/';
70 | path = unalias(name, loaderPath);
71 |
72 | if (has.call(cache, path)) return cache[path].exports;
73 | if (has.call(modules, path)) return initModule(path, modules[path]);
74 |
75 | var dirIndex = expand(path, './index');
76 | if (has.call(cache, dirIndex)) return cache[dirIndex].exports;
77 | if (has.call(modules, dirIndex)) return initModule(dirIndex, modules[dirIndex]);
78 |
79 | throw new Error('Cannot find module "' + name + '" from '+ '"' + loaderPath + '"');
80 | };
81 |
82 | require.alias = function(from, to) {
83 | aliases[to] = from;
84 | };
85 |
86 | require.register = require.define = function(bundle, fn) {
87 | if (typeof bundle === 'object') {
88 | for (var key in bundle) {
89 | if (has.call(bundle, key)) {
90 | modules[key] = bundle[key];
91 | }
92 | }
93 | } else {
94 | modules[bundle] = fn;
95 | }
96 | };
97 |
98 | require.list = function() {
99 | var result = [];
100 | for (var item in modules) {
101 | if (has.call(modules, item)) {
102 | result.push(item);
103 | }
104 | }
105 | return result;
106 | };
107 |
108 | require.brunch = true;
109 | require._cache = cache;
110 | globals.require = require;
111 | })();
112 | (function() {
113 | var global = window;
114 | var __shims = {assert: ({}),buffer: ({}),child_process: ({}),cluster: ({}),crypto: ({}),dgram: ({}),dns: ({}),domain: ({}),events: ({}),fs: ({}),http: ({}),https: ({}),net: ({}),os: ({}),path: ({}),punycode: ({}),querystring: ({}),readline: ({}),repl: ({}),string_decoder: ({}),tls: ({}),tty: ({}),url: ({}),util: ({}),vm: ({}),zlib: ({}),process: ({"env":{}})};
115 | var process = __shims.process;
116 |
117 | var __makeRequire = function(r, __brmap) {
118 | return function(name) {
119 | if (__brmap[name] !== undefined) name = __brmap[name];
120 | name = name.replace(".js", "");
121 | return ["assert","buffer","child_process","cluster","crypto","dgram","dns","domain","events","fs","http","https","net","os","path","punycode","querystring","readline","repl","string_decoder","tls","tty","url","util","vm","zlib","process"].indexOf(name) === -1 ? r(name) : __shims[name];
122 | }
123 | };
124 | require.register('phoenix', function(exports,req,module){
125 | var require = __makeRequire((function(n) { return req(n.replace('./', 'phoenix//priv/static/')); }), {});
126 | (function(exports,require,module) {
127 | (function(exports){
128 | "use strict";
129 |
130 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
131 |
132 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
133 |
134 | Object.defineProperty(exports, "__esModule", {
135 | value: true
136 | });
137 |
138 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
139 |
140 | // Phoenix Channels JavaScript client
141 | //
142 | // ## Socket Connection
143 | //
144 | // A single connection is established to the server and
145 | // channels are mulitplexed over the connection.
146 | // Connect to the server using the `Socket` class:
147 | //
148 | // let socket = new Socket("/ws", {params: {userToken: "123"}})
149 | // socket.connect()
150 | //
151 | // The `Socket` constructor takes the mount point of the socket,
152 | // the authentication params, as well as options that can be found in
153 | // the Socket docs, such as configuring the `LongPoll` transport, and
154 | // heartbeat.
155 | //
156 | // ## Channels
157 | //
158 | // Channels are isolated, concurrent processes on the server that
159 | // subscribe to topics and broker events between the client and server.
160 | // To join a channel, you must provide the topic, and channel params for
161 | // authorization. Here's an example chat room example where `"new_msg"`
162 | // events are listened for, messages are pushed to the server, and
163 | // the channel is joined with ok/error/timeout matches:
164 | //
165 | // let channel = socket.channel("rooms:123", {token: roomToken})
166 | // channel.on("new_msg", msg => console.log("Got message", msg) )
167 | // $input.onEnter( e => {
168 | // channel.push("new_msg", {body: e.target.val}, 10000)
169 | // .receive("ok", (msg) => console.log("created message", msg) )
170 | // .receive("error", (reasons) => console.log("create failed", reasons) )
171 | // .receive("timeout", () => console.log("Networking issue...") )
172 | // })
173 | // channel.join()
174 | // .receive("ok", ({messages}) => console.log("catching up", messages) )
175 | // .receive("error", ({reason}) => console.log("failed join", reason) )
176 | // .receive("timeout", () => console.log("Networking issue. Still waiting...") )
177 | //
178 | //
179 | // ## Joining
180 | //
181 | // Creating a channel with `socket.channel(topic, params)`, binds the params to
182 | // `channel.params`, which are sent up on `channel.join()`.
183 | // Subsequent rejoins will send up the modified params for
184 | // updating authorization params, or passing up last_message_id information.
185 | // Successful joins receive an "ok" status, while unsuccessful joins
186 | // receive "error".
187 | //
188 | //
189 | // ## Pushing Messages
190 | //
191 | // From the previous example, we can see that pushing messages to the server
192 | // can be done with `channel.push(eventName, payload)` and we can optionally
193 | // receive responses from the push. Additionally, we can use
194 | // `receive("timeout", callback)` to abort waiting for our other `receive` hooks
195 | // and take action after some period of waiting. The default timeout is 5000ms.
196 | //
197 | //
198 | // ## Socket Hooks
199 | //
200 | // Lifecycle events of the multiplexed connection can be hooked into via
201 | // `socket.onError()` and `socket.onClose()` events, ie:
202 | //
203 | // socket.onError( () => console.log("there was an error with the connection!") )
204 | // socket.onClose( () => console.log("the connection dropped") )
205 | //
206 | //
207 | // ## Channel Hooks
208 | //
209 | // For each joined channel, you can bind to `onError` and `onClose` events
210 | // to monitor the channel lifecycle, ie:
211 | //
212 | // channel.onError( () => console.log("there was an error!") )
213 | // channel.onClose( () => console.log("the channel has gone away gracefully") )
214 | //
215 | // ### onError hooks
216 | //
217 | // `onError` hooks are invoked if the socket connection drops, or the channel
218 | // crashes on the server. In either case, a channel rejoin is attemtped
219 | // automatically in an exponential backoff manner.
220 | //
221 | // ### onClose hooks
222 | //
223 | // `onClose` hooks are invoked only in two cases. 1) the channel explicitly
224 | // closed on the server, or 2). The client explicitly closed, by calling
225 | // `channel.leave()`
226 | //
227 |
228 | var VSN = "1.0.0";
229 | var SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 };
230 | var DEFAULT_TIMEOUT = 10000;
231 | var CHANNEL_STATES = {
232 | closed: "closed",
233 | errored: "errored",
234 | joined: "joined",
235 | joining: "joining"
236 | };
237 | var CHANNEL_EVENTS = {
238 | close: "phx_close",
239 | error: "phx_error",
240 | join: "phx_join",
241 | reply: "phx_reply",
242 | leave: "phx_leave"
243 | };
244 | var TRANSPORTS = {
245 | longpoll: "longpoll",
246 | websocket: "websocket"
247 | };
248 |
249 | var Push = function () {
250 |
251 | // Initializes the Push
252 | //
253 | // channel - The Channel
254 | // event - The event, for example `"phx_join"`
255 | // payload - The payload, for example `{user_id: 123}`
256 | // timeout - The push timeout in milliseconds
257 | //
258 |
259 | function Push(channel, event, payload, timeout) {
260 | _classCallCheck(this, Push);
261 |
262 | this.channel = channel;
263 | this.event = event;
264 | this.payload = payload || {};
265 | this.receivedResp = null;
266 | this.timeout = timeout;
267 | this.timeoutTimer = null;
268 | this.recHooks = [];
269 | this.sent = false;
270 | }
271 |
272 | _createClass(Push, [{
273 | key: "resend",
274 | value: function resend(timeout) {
275 | this.timeout = timeout;
276 | this.cancelRefEvent();
277 | this.ref = null;
278 | this.refEvent = null;
279 | this.receivedResp = null;
280 | this.sent = false;
281 | this.send();
282 | }
283 | }, {
284 | key: "send",
285 | value: function send() {
286 | if (this.hasReceived("timeout")) {
287 | return;
288 | }
289 | this.startTimeout();
290 | this.sent = true;
291 | this.channel.socket.push({
292 | topic: this.channel.topic,
293 | event: this.event,
294 | payload: this.payload,
295 | ref: this.ref
296 | });
297 | }
298 | }, {
299 | key: "receive",
300 | value: function receive(status, callback) {
301 | if (this.hasReceived(status)) {
302 | callback(this.receivedResp.response);
303 | }
304 |
305 | this.recHooks.push({ status: status, callback: callback });
306 | return this;
307 | }
308 |
309 | // private
310 |
311 | }, {
312 | key: "matchReceive",
313 | value: function matchReceive(_ref) {
314 | var status = _ref.status;
315 | var response = _ref.response;
316 | var ref = _ref.ref;
317 |
318 | this.recHooks.filter(function (h) {
319 | return h.status === status;
320 | }).forEach(function (h) {
321 | return h.callback(response);
322 | });
323 | }
324 | }, {
325 | key: "cancelRefEvent",
326 | value: function cancelRefEvent() {
327 | if (!this.refEvent) {
328 | return;
329 | }
330 | this.channel.off(this.refEvent);
331 | }
332 | }, {
333 | key: "cancelTimeout",
334 | value: function cancelTimeout() {
335 | clearTimeout(this.timeoutTimer);
336 | this.timeoutTimer = null;
337 | }
338 | }, {
339 | key: "startTimeout",
340 | value: function startTimeout() {
341 | var _this = this;
342 |
343 | if (this.timeoutTimer) {
344 | return;
345 | }
346 | this.ref = this.channel.socket.makeRef();
347 | this.refEvent = this.channel.replyEventName(this.ref);
348 |
349 | this.channel.on(this.refEvent, function (payload) {
350 | _this.cancelRefEvent();
351 | _this.cancelTimeout();
352 | _this.receivedResp = payload;
353 | _this.matchReceive(payload);
354 | });
355 |
356 | this.timeoutTimer = setTimeout(function () {
357 | _this.trigger("timeout", {});
358 | }, this.timeout);
359 | }
360 | }, {
361 | key: "hasReceived",
362 | value: function hasReceived(status) {
363 | return this.receivedResp && this.receivedResp.status === status;
364 | }
365 | }, {
366 | key: "trigger",
367 | value: function trigger(status, response) {
368 | this.channel.trigger(this.refEvent, { status: status, response: response });
369 | }
370 | }]);
371 |
372 | return Push;
373 | }();
374 |
375 | var Channel = exports.Channel = function () {
376 | function Channel(topic, params, socket) {
377 | var _this2 = this;
378 |
379 | _classCallCheck(this, Channel);
380 |
381 | this.state = CHANNEL_STATES.closed;
382 | this.topic = topic;
383 | this.params = params || {};
384 | this.socket = socket;
385 | this.bindings = [];
386 | this.timeout = this.socket.timeout;
387 | this.joinedOnce = false;
388 | this.joinPush = new Push(this, CHANNEL_EVENTS.join, this.params, this.timeout);
389 | this.pushBuffer = [];
390 | this.rejoinTimer = new Timer(function () {
391 | return _this2.rejoinUntilConnected();
392 | }, this.socket.reconnectAfterMs);
393 | this.joinPush.receive("ok", function () {
394 | _this2.state = CHANNEL_STATES.joined;
395 | _this2.rejoinTimer.reset();
396 | _this2.pushBuffer.forEach(function (pushEvent) {
397 | return pushEvent.send();
398 | });
399 | _this2.pushBuffer = [];
400 | });
401 | this.onClose(function () {
402 | _this2.socket.log("channel", "close " + _this2.topic);
403 | _this2.state = CHANNEL_STATES.closed;
404 | _this2.socket.remove(_this2);
405 | });
406 | this.onError(function (reason) {
407 | _this2.socket.log("channel", "error " + _this2.topic, reason);
408 | _this2.state = CHANNEL_STATES.errored;
409 | _this2.rejoinTimer.scheduleTimeout();
410 | });
411 | this.joinPush.receive("timeout", function () {
412 | if (_this2.state !== CHANNEL_STATES.joining) {
413 | return;
414 | }
415 |
416 | _this2.socket.log("channel", "timeout " + _this2.topic, _this2.joinPush.timeout);
417 | _this2.state = CHANNEL_STATES.errored;
418 | _this2.rejoinTimer.scheduleTimeout();
419 | });
420 | this.on(CHANNEL_EVENTS.reply, function (payload, ref) {
421 | _this2.trigger(_this2.replyEventName(ref), payload);
422 | });
423 | }
424 |
425 | _createClass(Channel, [{
426 | key: "rejoinUntilConnected",
427 | value: function rejoinUntilConnected() {
428 | this.rejoinTimer.scheduleTimeout();
429 | if (this.socket.isConnected()) {
430 | this.rejoin();
431 | }
432 | }
433 | }, {
434 | key: "join",
435 | value: function join() {
436 | var timeout = arguments.length <= 0 || arguments[0] === undefined ? this.timeout : arguments[0];
437 |
438 | if (this.joinedOnce) {
439 | throw "tried to join multiple times. 'join' can only be called a single time per channel instance";
440 | } else {
441 | this.joinedOnce = true;
442 | }
443 | this.rejoin(timeout);
444 | return this.joinPush;
445 | }
446 | }, {
447 | key: "onClose",
448 | value: function onClose(callback) {
449 | this.on(CHANNEL_EVENTS.close, callback);
450 | }
451 | }, {
452 | key: "onError",
453 | value: function onError(callback) {
454 | this.on(CHANNEL_EVENTS.error, function (reason) {
455 | return callback(reason);
456 | });
457 | }
458 | }, {
459 | key: "on",
460 | value: function on(event, callback) {
461 | this.bindings.push({ event: event, callback: callback });
462 | }
463 | }, {
464 | key: "off",
465 | value: function off(event) {
466 | this.bindings = this.bindings.filter(function (bind) {
467 | return bind.event !== event;
468 | });
469 | }
470 | }, {
471 | key: "canPush",
472 | value: function canPush() {
473 | return this.socket.isConnected() && this.state === CHANNEL_STATES.joined;
474 | }
475 | }, {
476 | key: "push",
477 | value: function push(event, payload) {
478 | var timeout = arguments.length <= 2 || arguments[2] === undefined ? this.timeout : arguments[2];
479 |
480 | if (!this.joinedOnce) {
481 | throw "tried to push '" + event + "' to '" + this.topic + "' before joining. Use channel.join() before pushing events";
482 | }
483 | var pushEvent = new Push(this, event, payload, timeout);
484 | if (this.canPush()) {
485 | pushEvent.send();
486 | } else {
487 | pushEvent.startTimeout();
488 | this.pushBuffer.push(pushEvent);
489 | }
490 |
491 | return pushEvent;
492 | }
493 |
494 | // Leaves the channel
495 | //
496 | // Unsubscribes from server events, and
497 | // instructs channel to terminate on server
498 | //
499 | // Triggers onClose() hooks
500 | //
501 | // To receive leave acknowledgements, use the a `receive`
502 | // hook to bind to the server ack, ie:
503 | //
504 | // channel.leave().receive("ok", () => alert("left!") )
505 | //
506 |
507 | }, {
508 | key: "leave",
509 | value: function leave() {
510 | var _this3 = this;
511 |
512 | var timeout = arguments.length <= 0 || arguments[0] === undefined ? this.timeout : arguments[0];
513 |
514 | var onClose = function onClose() {
515 | _this3.socket.log("channel", "leave " + _this3.topic);
516 | _this3.trigger(CHANNEL_EVENTS.close, "leave");
517 | };
518 | var leavePush = new Push(this, CHANNEL_EVENTS.leave, {}, timeout);
519 | leavePush.receive("ok", function () {
520 | return onClose();
521 | }).receive("timeout", function () {
522 | return onClose();
523 | });
524 | leavePush.send();
525 | if (!this.canPush()) {
526 | leavePush.trigger("ok", {});
527 | }
528 |
529 | return leavePush;
530 | }
531 |
532 | // Overridable message hook
533 | //
534 | // Receives all events for specialized message handling
535 |
536 | }, {
537 | key: "onMessage",
538 | value: function onMessage(event, payload, ref) {}
539 |
540 | // private
541 |
542 | }, {
543 | key: "isMember",
544 | value: function isMember(topic) {
545 | return this.topic === topic;
546 | }
547 | }, {
548 | key: "sendJoin",
549 | value: function sendJoin(timeout) {
550 | this.state = CHANNEL_STATES.joining;
551 | this.joinPush.resend(timeout);
552 | }
553 | }, {
554 | key: "rejoin",
555 | value: function rejoin() {
556 | var timeout = arguments.length <= 0 || arguments[0] === undefined ? this.timeout : arguments[0];
557 | this.sendJoin(timeout);
558 | }
559 | }, {
560 | key: "trigger",
561 | value: function trigger(triggerEvent, payload, ref) {
562 | this.onMessage(triggerEvent, payload, ref);
563 | this.bindings.filter(function (bind) {
564 | return bind.event === triggerEvent;
565 | }).map(function (bind) {
566 | return bind.callback(payload, ref);
567 | });
568 | }
569 | }, {
570 | key: "replyEventName",
571 | value: function replyEventName(ref) {
572 | return "chan_reply_" + ref;
573 | }
574 | }]);
575 |
576 | return Channel;
577 | }();
578 |
579 | var Socket = exports.Socket = function () {
580 |
581 | // Initializes the Socket
582 | //
583 | // endPoint - The string WebSocket endpoint, ie, "ws://example.com/ws",
584 | // "wss://example.com"
585 | // "/ws" (inherited host & protocol)
586 | // opts - Optional configuration
587 | // transport - The Websocket Transport, for example WebSocket or Phoenix.LongPoll.
588 | // Defaults to WebSocket with automatic LongPoll fallback.
589 | // timeout - The default timeout in milliseconds to trigger push timeouts.
590 | // Defaults `DEFAULT_TIMEOUT`
591 | // heartbeatIntervalMs - The millisec interval to send a heartbeat message
592 | // reconnectAfterMs - The optional function that returns the millsec
593 | // reconnect interval. Defaults to stepped backoff of:
594 | //
595 | // function(tries){
596 | // return [1000, 5000, 10000][tries - 1] || 10000
597 | // }
598 | //
599 | // logger - The optional function for specialized logging, ie:
600 | // `logger: (kind, msg, data) => { console.log(`${kind}: ${msg}`, data) }
601 | //
602 | // longpollerTimeout - The maximum timeout of a long poll AJAX request.
603 | // Defaults to 20s (double the server long poll timer).
604 | //
605 | // params - The optional params to pass when connecting
606 | //
607 | // For IE8 support use an ES5-shim (https://github.com/es-shims/es5-shim)
608 | //
609 |
610 | function Socket(endPoint) {
611 | var _this4 = this;
612 |
613 | var opts = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
614 |
615 | _classCallCheck(this, Socket);
616 |
617 | this.stateChangeCallbacks = { open: [], close: [], error: [], message: [] };
618 | this.channels = [];
619 | this.sendBuffer = [];
620 | this.ref = 0;
621 | this.timeout = opts.timeout || DEFAULT_TIMEOUT;
622 | this.transport = opts.transport || window.WebSocket || LongPoll;
623 | this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 30000;
624 | this.reconnectAfterMs = opts.reconnectAfterMs || function (tries) {
625 | return [1000, 2000, 5000, 10000][tries - 1] || 10000;
626 | };
627 | this.logger = opts.logger || function () {}; // noop
628 | this.longpollerTimeout = opts.longpollerTimeout || 20000;
629 | this.params = opts.params || {};
630 | this.endPoint = endPoint + "/" + TRANSPORTS.websocket;
631 | this.reconnectTimer = new Timer(function () {
632 | _this4.disconnect(function () {
633 | return _this4.connect();
634 | });
635 | }, this.reconnectAfterMs);
636 | }
637 |
638 | _createClass(Socket, [{
639 | key: "protocol",
640 | value: function protocol() {
641 | return location.protocol.match(/^https/) ? "wss" : "ws";
642 | }
643 | }, {
644 | key: "endPointURL",
645 | value: function endPointURL() {
646 | var uri = Ajax.appendParams(Ajax.appendParams(this.endPoint, this.params), { vsn: VSN });
647 | if (uri.charAt(0) !== "/") {
648 | return uri;
649 | }
650 | if (uri.charAt(1) === "/") {
651 | return this.protocol() + ":" + uri;
652 | }
653 |
654 | return this.protocol() + "://" + location.host + uri;
655 | }
656 | }, {
657 | key: "disconnect",
658 | value: function disconnect(callback, code, reason) {
659 | if (this.conn) {
660 | this.conn.onclose = function () {}; // noop
661 | if (code) {
662 | this.conn.close(code, reason || "");
663 | } else {
664 | this.conn.close();
665 | }
666 | this.conn = null;
667 | }
668 | callback && callback();
669 | }
670 |
671 | // params - The params to send when connecting, for example `{user_id: userToken}`
672 |
673 | }, {
674 | key: "connect",
675 | value: function connect(params) {
676 | var _this5 = this;
677 |
678 | if (params) {
679 | console && console.log("passing params to connect is deprecated. Instead pass :params to the Socket constructor");
680 | this.params = params;
681 | }
682 | if (this.conn) {
683 | return;
684 | }
685 |
686 | this.conn = new this.transport(this.endPointURL());
687 | this.conn.timeout = this.longpollerTimeout;
688 | this.conn.onopen = function () {
689 | return _this5.onConnOpen();
690 | };
691 | this.conn.onerror = function (error) {
692 | return _this5.onConnError(error);
693 | };
694 | this.conn.onmessage = function (event) {
695 | return _this5.onConnMessage(event);
696 | };
697 | this.conn.onclose = function (event) {
698 | return _this5.onConnClose(event);
699 | };
700 | }
701 |
702 | // Logs the message. Override `this.logger` for specialized logging. noops by default
703 |
704 | }, {
705 | key: "log",
706 | value: function log(kind, msg, data) {
707 | this.logger(kind, msg, data);
708 | }
709 |
710 | // Registers callbacks for connection state change events
711 | //
712 | // Examples
713 | //
714 | // socket.onError(function(error){ alert("An error occurred") })
715 | //
716 |
717 | }, {
718 | key: "onOpen",
719 | value: function onOpen(callback) {
720 | this.stateChangeCallbacks.open.push(callback);
721 | }
722 | }, {
723 | key: "onClose",
724 | value: function onClose(callback) {
725 | this.stateChangeCallbacks.close.push(callback);
726 | }
727 | }, {
728 | key: "onError",
729 | value: function onError(callback) {
730 | this.stateChangeCallbacks.error.push(callback);
731 | }
732 | }, {
733 | key: "onMessage",
734 | value: function onMessage(callback) {
735 | this.stateChangeCallbacks.message.push(callback);
736 | }
737 | }, {
738 | key: "onConnOpen",
739 | value: function onConnOpen() {
740 | var _this6 = this;
741 |
742 | this.log("transport", "connected to " + this.endPointURL(), this.transport.prototype);
743 | this.flushSendBuffer();
744 | this.reconnectTimer.reset();
745 | if (!this.conn.skipHeartbeat) {
746 | clearInterval(this.heartbeatTimer);
747 | this.heartbeatTimer = setInterval(function () {
748 | return _this6.sendHeartbeat();
749 | }, this.heartbeatIntervalMs);
750 | }
751 | this.stateChangeCallbacks.open.forEach(function (callback) {
752 | return callback();
753 | });
754 | }
755 | }, {
756 | key: "onConnClose",
757 | value: function onConnClose(event) {
758 | this.log("transport", "close", event);
759 | this.triggerChanError();
760 | clearInterval(this.heartbeatTimer);
761 | this.reconnectTimer.scheduleTimeout();
762 | this.stateChangeCallbacks.close.forEach(function (callback) {
763 | return callback(event);
764 | });
765 | }
766 | }, {
767 | key: "onConnError",
768 | value: function onConnError(error) {
769 | this.log("transport", error);
770 | this.triggerChanError();
771 | this.stateChangeCallbacks.error.forEach(function (callback) {
772 | return callback(error);
773 | });
774 | }
775 | }, {
776 | key: "triggerChanError",
777 | value: function triggerChanError() {
778 | this.channels.forEach(function (channel) {
779 | return channel.trigger(CHANNEL_EVENTS.error);
780 | });
781 | }
782 | }, {
783 | key: "connectionState",
784 | value: function connectionState() {
785 | switch (this.conn && this.conn.readyState) {
786 | case SOCKET_STATES.connecting:
787 | return "connecting";
788 | case SOCKET_STATES.open:
789 | return "open";
790 | case SOCKET_STATES.closing:
791 | return "closing";
792 | default:
793 | return "closed";
794 | }
795 | }
796 | }, {
797 | key: "isConnected",
798 | value: function isConnected() {
799 | return this.connectionState() === "open";
800 | }
801 | }, {
802 | key: "remove",
803 | value: function remove(channel) {
804 | this.channels = this.channels.filter(function (c) {
805 | return !c.isMember(channel.topic);
806 | });
807 | }
808 | }, {
809 | key: "channel",
810 | value: function channel(topic) {
811 | var chanParams = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
812 |
813 | var chan = new Channel(topic, chanParams, this);
814 | this.channels.push(chan);
815 | return chan;
816 | }
817 | }, {
818 | key: "push",
819 | value: function push(data) {
820 | var _this7 = this;
821 |
822 | var topic = data.topic;
823 | var event = data.event;
824 | var payload = data.payload;
825 | var ref = data.ref;
826 |
827 | var callback = function callback() {
828 | return _this7.conn.send(JSON.stringify(data));
829 | };
830 | this.log("push", topic + " " + event + " (" + ref + ")", payload);
831 | if (this.isConnected()) {
832 | callback();
833 | } else {
834 | this.sendBuffer.push(callback);
835 | }
836 | }
837 |
838 | // Return the next message ref, accounting for overflows
839 |
840 | }, {
841 | key: "makeRef",
842 | value: function makeRef() {
843 | var newRef = this.ref + 1;
844 | if (newRef === this.ref) {
845 | this.ref = 0;
846 | } else {
847 | this.ref = newRef;
848 | }
849 |
850 | return this.ref.toString();
851 | }
852 | }, {
853 | key: "sendHeartbeat",
854 | value: function sendHeartbeat() {
855 | if (!this.isConnected()) {
856 | return;
857 | }
858 | this.push({ topic: "phoenix", event: "heartbeat", payload: {}, ref: this.makeRef() });
859 | }
860 | }, {
861 | key: "flushSendBuffer",
862 | value: function flushSendBuffer() {
863 | if (this.isConnected() && this.sendBuffer.length > 0) {
864 | this.sendBuffer.forEach(function (callback) {
865 | return callback();
866 | });
867 | this.sendBuffer = [];
868 | }
869 | }
870 | }, {
871 | key: "onConnMessage",
872 | value: function onConnMessage(rawMessage) {
873 | var msg = JSON.parse(rawMessage.data);
874 | var topic = msg.topic;
875 | var event = msg.event;
876 | var payload = msg.payload;
877 | var ref = msg.ref;
878 |
879 | this.log("receive", (payload.status || "") + " " + topic + " " + event + " " + (ref && "(" + ref + ")" || ""), payload);
880 | this.channels.filter(function (channel) {
881 | return channel.isMember(topic);
882 | }).forEach(function (channel) {
883 | return channel.trigger(event, payload, ref);
884 | });
885 | this.stateChangeCallbacks.message.forEach(function (callback) {
886 | return callback(msg);
887 | });
888 | }
889 | }]);
890 |
891 | return Socket;
892 | }();
893 |
894 | var LongPoll = exports.LongPoll = function () {
895 | function LongPoll(endPoint) {
896 | _classCallCheck(this, LongPoll);
897 |
898 | this.endPoint = null;
899 | this.token = null;
900 | this.skipHeartbeat = true;
901 | this.onopen = function () {}; // noop
902 | this.onerror = function () {}; // noop
903 | this.onmessage = function () {}; // noop
904 | this.onclose = function () {}; // noop
905 | this.pollEndpoint = this.normalizeEndpoint(endPoint);
906 | this.readyState = SOCKET_STATES.connecting;
907 |
908 | this.poll();
909 | }
910 |
911 | _createClass(LongPoll, [{
912 | key: "normalizeEndpoint",
913 | value: function normalizeEndpoint(endPoint) {
914 | return endPoint.replace("ws://", "http://").replace("wss://", "https://").replace(new RegExp("(.*)\/" + TRANSPORTS.websocket), "$1/" + TRANSPORTS.longpoll);
915 | }
916 | }, {
917 | key: "endpointURL",
918 | value: function endpointURL() {
919 | return Ajax.appendParams(this.pollEndpoint, { token: this.token });
920 | }
921 | }, {
922 | key: "closeAndRetry",
923 | value: function closeAndRetry() {
924 | this.close();
925 | this.readyState = SOCKET_STATES.connecting;
926 | }
927 | }, {
928 | key: "ontimeout",
929 | value: function ontimeout() {
930 | this.onerror("timeout");
931 | this.closeAndRetry();
932 | }
933 | }, {
934 | key: "poll",
935 | value: function poll() {
936 | var _this8 = this;
937 |
938 | if (!(this.readyState === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting)) {
939 | return;
940 | }
941 |
942 | Ajax.request("GET", this.endpointURL(), "application/json", null, this.timeout, this.ontimeout.bind(this), function (resp) {
943 | if (resp) {
944 | var status = resp.status;
945 | var token = resp.token;
946 | var messages = resp.messages;
947 |
948 | _this8.token = token;
949 | } else {
950 | var status = 0;
951 | }
952 |
953 | switch (status) {
954 | case 200:
955 | messages.forEach(function (msg) {
956 | return _this8.onmessage({ data: JSON.stringify(msg) });
957 | });
958 | _this8.poll();
959 | break;
960 | case 204:
961 | _this8.poll();
962 | break;
963 | case 410:
964 | _this8.readyState = SOCKET_STATES.open;
965 | _this8.onopen();
966 | _this8.poll();
967 | break;
968 | case 0:
969 | case 500:
970 | _this8.onerror();
971 | _this8.closeAndRetry();
972 | break;
973 | default:
974 | throw "unhandled poll status " + status;
975 | }
976 | });
977 | }
978 | }, {
979 | key: "send",
980 | value: function send(body) {
981 | var _this9 = this;
982 |
983 | Ajax.request("POST", this.endpointURL(), "application/json", body, this.timeout, this.onerror.bind(this, "timeout"), function (resp) {
984 | if (!resp || resp.status !== 200) {
985 | _this9.onerror(status);
986 | _this9.closeAndRetry();
987 | }
988 | });
989 | }
990 | }, {
991 | key: "close",
992 | value: function close(code, reason) {
993 | this.readyState = SOCKET_STATES.closed;
994 | this.onclose();
995 | }
996 | }]);
997 |
998 | return LongPoll;
999 | }();
1000 |
1001 | var Ajax = exports.Ajax = function () {
1002 | function Ajax() {
1003 | _classCallCheck(this, Ajax);
1004 | }
1005 |
1006 | _createClass(Ajax, null, [{
1007 | key: "request",
1008 | value: function request(method, endPoint, accept, body, timeout, ontimeout, callback) {
1009 | if (window.XDomainRequest) {
1010 | var req = new XDomainRequest(); // IE8, IE9
1011 | this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback);
1012 | } else {
1013 | var req = window.XMLHttpRequest ? new XMLHttpRequest() : // IE7+, Firefox, Chrome, Opera, Safari
1014 | new ActiveXObject("Microsoft.XMLHTTP"); // IE6, IE5
1015 | this.xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback);
1016 | }
1017 | }
1018 | }, {
1019 | key: "xdomainRequest",
1020 | value: function xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback) {
1021 | var _this10 = this;
1022 |
1023 | req.timeout = timeout;
1024 | req.open(method, endPoint);
1025 | req.onload = function () {
1026 | var response = _this10.parseJSON(req.responseText);
1027 | callback && callback(response);
1028 | };
1029 | if (ontimeout) {
1030 | req.ontimeout = ontimeout;
1031 | }
1032 |
1033 | // Work around bug in IE9 that requires an attached onprogress handler
1034 | req.onprogress = function () {};
1035 |
1036 | req.send(body);
1037 | }
1038 | }, {
1039 | key: "xhrRequest",
1040 | value: function xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback) {
1041 | var _this11 = this;
1042 |
1043 | req.timeout = timeout;
1044 | req.open(method, endPoint, true);
1045 | req.setRequestHeader("Content-Type", accept);
1046 | req.onerror = function () {
1047 | callback && callback(null);
1048 | };
1049 | req.onreadystatechange = function () {
1050 | if (req.readyState === _this11.states.complete && callback) {
1051 | var response = _this11.parseJSON(req.responseText);
1052 | callback(response);
1053 | }
1054 | };
1055 | if (ontimeout) {
1056 | req.ontimeout = ontimeout;
1057 | }
1058 |
1059 | req.send(body);
1060 | }
1061 | }, {
1062 | key: "parseJSON",
1063 | value: function parseJSON(resp) {
1064 | return resp && resp !== "" ? JSON.parse(resp) : null;
1065 | }
1066 | }, {
1067 | key: "serialize",
1068 | value: function serialize(obj, parentKey) {
1069 | var queryStr = [];
1070 | for (var key in obj) {
1071 | if (!obj.hasOwnProperty(key)) {
1072 | continue;
1073 | }
1074 | var paramKey = parentKey ? parentKey + "[" + key + "]" : key;
1075 | var paramVal = obj[key];
1076 | if ((typeof paramVal === "undefined" ? "undefined" : _typeof(paramVal)) === "object") {
1077 | queryStr.push(this.serialize(paramVal, paramKey));
1078 | } else {
1079 | queryStr.push(encodeURIComponent(paramKey) + "=" + encodeURIComponent(paramVal));
1080 | }
1081 | }
1082 | return queryStr.join("&");
1083 | }
1084 | }, {
1085 | key: "appendParams",
1086 | value: function appendParams(url, params) {
1087 | if (Object.keys(params).length === 0) {
1088 | return url;
1089 | }
1090 |
1091 | var prefix = url.match(/\?/) ? "&" : "?";
1092 | return "" + url + prefix + this.serialize(params);
1093 | }
1094 | }]);
1095 |
1096 | return Ajax;
1097 | }();
1098 |
1099 | Ajax.states = { complete: 4 };
1100 |
1101 | // Creates a timer that accepts a `timerCalc` function to perform
1102 | // calculated timeout retries, such as exponential backoff.
1103 | //
1104 | // ## Examples
1105 | //
1106 | // let reconnectTimer = new Timer(() => this.connect(), function(tries){
1107 | // return [1000, 5000, 10000][tries - 1] || 10000
1108 | // })
1109 | // reconnectTimer.scheduleTimeout() // fires after 1000
1110 | // reconnectTimer.scheduleTimeout() // fires after 5000
1111 | // reconnectTimer.reset()
1112 | // reconnectTimer.scheduleTimeout() // fires after 1000
1113 | //
1114 |
1115 | var Timer = function () {
1116 | function Timer(callback, timerCalc) {
1117 | _classCallCheck(this, Timer);
1118 |
1119 | this.callback = callback;
1120 | this.timerCalc = timerCalc;
1121 | this.timer = null;
1122 | this.tries = 0;
1123 | }
1124 |
1125 | _createClass(Timer, [{
1126 | key: "reset",
1127 | value: function reset() {
1128 | this.tries = 0;
1129 | clearTimeout(this.timer);
1130 | }
1131 |
1132 | // Cancels any previous scheduleTimeout and schedules callback
1133 |
1134 | }, {
1135 | key: "scheduleTimeout",
1136 | value: function scheduleTimeout() {
1137 | var _this12 = this;
1138 |
1139 | clearTimeout(this.timer);
1140 |
1141 | this.timer = setTimeout(function () {
1142 | _this12.tries = _this12.tries + 1;
1143 | _this12.callback();
1144 | }, this.timerCalc(this.tries + 1));
1145 | }
1146 | }]);
1147 |
1148 | return Timer;
1149 | }();
1150 |
1151 |
1152 | })(typeof(exports) === "undefined" ? window.Phoenix = window.Phoenix || {} : exports);
1153 |
1154 | })(exports,require,module);
1155 | });
1156 | require.register('phoenix_html', function(exports,req,module){
1157 | var require = __makeRequire((function(n) { return req(n.replace('./', 'phoenix_html//priv/static/')); }), {});
1158 | (function(exports,require,module) {
1159 | 'use strict';
1160 |
1161 | // Although ^=parent is not technically correct,
1162 | // we need to use it in order to get IE8 support.
1163 | var elements = document.querySelectorAll('[data-submit^=parent]');
1164 | var len = elements.length;
1165 |
1166 | for (var i = 0; i < len; ++i) {
1167 | elements[i].addEventListener('click', function (event) {
1168 | var message = this.getAttribute("data-confirm");
1169 | if (message === null || confirm(message)) {
1170 | this.parentNode.submit();
1171 | };
1172 | event.preventDefault();
1173 | return false;
1174 | }, false);
1175 | }
1176 |
1177 | ;
1178 | })(exports,require,module);
1179 | });
1180 | })();require.register("web/static/js/app", function(exports, require, module) {
1181 | "use strict";
1182 |
1183 | require("phoenix_html");
1184 | });
1185 |
1186 | ;require.register("web/static/js/socket", function(exports, require, module) {
1187 | "use strict";
1188 |
1189 | Object.defineProperty(exports, "__esModule", {
1190 | value: true
1191 | });
1192 |
1193 | var _phoenix = require("phoenix");
1194 |
1195 | var socket = new _phoenix.Socket("/socket", { params: { token: window.userToken } });
1196 |
1197 | // When you connect, you'll often need to authenticate the client.
1198 | // For example, imagine you have an authentication plug, `MyAuth`,
1199 | // which authenticates the session and assigns a `:current_user`.
1200 | // If the current user exists you can assign the user's token in
1201 | // the connection for use in the layout.
1202 | //
1203 | // In your "web/router.ex":
1204 | //
1205 | // pipeline :browser do
1206 | // ...
1207 | // plug MyAuth
1208 | // plug :put_user_token
1209 | // end
1210 | //
1211 | // defp put_user_token(conn, _) do
1212 | // if current_user = conn.assigns[:current_user] do
1213 | // token = Phoenix.Token.sign(conn, "user socket", current_user.id)
1214 | // assign(conn, :user_token, token)
1215 | // else
1216 | // conn
1217 | // end
1218 | // end
1219 | //
1220 | // Now you need to pass this token to JavaScript. You can do so
1221 | // inside a script tag in "web/templates/layout/app.html.eex":
1222 | //
1223 | //
1224 | //
1225 | // You will need to verify the user token in the "connect/2" function
1226 | // in "web/channels/user_socket.ex":
1227 | //
1228 | // def connect(%{"token" => token}, socket) do
1229 | // # max_age: 1209600 is equivalent to two weeks in seconds
1230 | // case Phoenix.Token.verify(socket, "user socket", token, max_age: 1209600) do
1231 | // {:ok, user_id} ->
1232 | // {:ok, assign(socket, :user, user_id)}
1233 | // {:error, reason} ->
1234 | // :error
1235 | // end
1236 | // end
1237 | //
1238 | // Finally, pass the token on connect as below. Or remove it
1239 | // from connect if you don't care about authentication.
1240 |
1241 | // NOTE: The contents of this file will only be executed if
1242 | // you uncomment its entry in "web/static/js/app.js".
1243 |
1244 | // To use Phoenix channels, the first step is to import Socket
1245 | // and connect at the socket path in "lib/my_app/endpoint.ex":
1246 | socket.connect();
1247 |
1248 | // Now that you are connected, you can join channels with a topic:
1249 | var channel = socket.channel("topic:subtopic", {});
1250 | channel.join().receive("ok", function (resp) {
1251 | console.log("Joined successfully", resp);
1252 | }).receive("error", function (resp) {
1253 | console.log("Unable to join", resp);
1254 | });
1255 |
1256 | exports.default = socket;
1257 | });
1258 |
1259 | ;require('web/static/js/app');
1260 | //# sourceMappingURL=app.js.map
--------------------------------------------------------------------------------