Skip to content

Commit

Permalink
Call a method when an element becomes visible.
Browse files Browse the repository at this point in the history
  • Loading branch information
adamghill committed Sep 9, 2021
1 parent 018f4a5 commit 4406642
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 49 deletions.
3 changes: 3 additions & 0 deletions django_unicorn/static/unicorn/js/attribute.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export class Attribute {
this.isTarget = false;
this.isPartial = false;
this.isDirty = false;
this.isVisible = false;
this.isKey = false;
this.isError = false;
this.modifiers = {};
Expand Down Expand Up @@ -45,6 +46,8 @@ export class Attribute {
this.isPartial = true;
} else if (contains(this.name, ":dirty")) {
this.isDirty = true;
} else if (contains(this.name, ":visible")) {
this.isVisible = true;
} else if (this.name === "unicorn:key" || this.name === "u:key") {
this.isKey = true;
} else if (contains(this.name, ":error:")) {
Expand Down
42 changes: 42 additions & 0 deletions django_unicorn/static/unicorn/js/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export class Component {
this.modelEls = [];
this.loadingEls = [];
this.keyEls = [];
this.visibilityEls = [];
this.errors = {};
this.return = {};
this.poll = {};
Expand All @@ -53,6 +54,7 @@ export class Component {

this.init();
this.refreshEventListeners();
this.initVisibility();
this.initPolling();

this.callCalls(args.calls);
Expand Down Expand Up @@ -206,6 +208,10 @@ export class Component {
this.keyEls.push(element);
}

if (hasValue(element.visibility)) {
this.visibilityEls.push(element);
}

element.actions.forEach((action) => {
if (this.actionEvents[action.eventType]) {
this.actionEvents[action.eventType].push({ action, element });
Expand Down Expand Up @@ -255,6 +261,42 @@ export class Component {
});
}

/**
* Initializes `visible` elements.
*/
initVisibility() {
if (
typeof window !== "undefined" &&
"IntersectionObserver" in window &&
"IntersectionObserverEntry" in window &&
"intersectionRatio" in window.IntersectionObserverEntry.prototype
) {
this.visibilityEls.forEach((element) => {
const observer = new IntersectionObserver(
(entries) => {
const entry = entries[0];

if (entry.isIntersecting) {
this.callMethod(
element.visibility.method,
element.visibility.debounceTime,
element.partials,
(err) => {
if (err) {
console.error(err);
}
}
);
}
},
{ threshold: [element.visibility.threshold] }
);

observer.observe(element.el);
});
}
}

/**
* Handles poll errors.
* @param {Error} err Error.
Expand Down
14 changes: 14 additions & 0 deletions django_unicorn/static/unicorn/js/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export class Element {
this.actions = [];
this.partials = [];
this.target = null;
this.visibility = {};
this.key = null;
this.events = [];
this.errors = [];
Expand Down Expand Up @@ -97,6 +98,19 @@ export class Element {
} else {
this.partials.push({ target: attribute.value });
}
} else if (attribute.isVisible) {
let threshold = attribute.modifiers.threshold || 0;

if (threshold > 1) {
// Convert the whole number into a percentage
threshold /= 100;
}

this.visibility.method = attribute.value;
this.visibility.threshold = threshold;
this.visibility.debounceTime = attribute.modifiers.debounce
? parseInt(attribute.modifiers.debounce, 10) || 0
: 0;
} else if (attribute.eventType) {
const action = {};
action.name = attribute.value;
Expand Down
4 changes: 4 additions & 0 deletions example/unicorn/components/js.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class JsView(UnicornView):
)
selected_state = ""
select2_datetime = now()
scroll_counter = 0

def call_javascript(self):
self.call("callAlert", "world")
Expand All @@ -30,5 +31,8 @@ def select_state(self, val, idx):
print("select_state called idx", idx)
self.selected_state = val

def increase_counter(self):
self.scroll_counter += 1

class Meta:
javascript_excludes = ("states",)
105 changes: 56 additions & 49 deletions example/unicorn/templates/unicorn/js.html
Original file line number Diff line number Diff line change
@@ -1,68 +1,75 @@
<div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/select2.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/select2.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/select2.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/select2.min.js"></script>

<script>
function callAlert(name) {
alert("hello, " + name);
}
<script>
function callAlert(name) {
alert("hello, " + name);
}

var HelloJs = (function() {
var self = {};
var HelloJs = (function () {
var self = {};

self.hello = function(name){
alert("Hello " + name)
}
self.hello = function (name) {
alert("Hello " + name)
}

return self;
}());
</script>
return self;
}());
</script>

<h2>
<button unicorn:click="call_javascript">Component view calls JavaScript</button>
<button unicorn:click="call_javascript_module">Component view calls JavaScript module</button>
</h2>

<div>
<h2>
<button unicorn:click="call_javascript">Component view calls JavaScript</button>
<button unicorn:click="call_javascript_module">Component view calls JavaScript module</button>
<code>javascript_exclude states</code>
</h2>
{% for state in states %}
{{ state }}
{% endfor %}
</div>

<div>
<h2>
<code>javascript_exclude states</code>
</h2>
{% for state in states %}
{{ state }}
{% endfor %}
</div>
<h2>Select2</h2>

<h2>Select2</h2>
<div u:ignore>
<select unicorn:model="selected_state" class="form-control" id="select2-example" required
onchange="Unicorn.call('js', 'select_state', this.value, this.selectedIndex);">
{% for state in states %}
<option value="{{ state }}">{{ state }}</option>
{% endfor %}
</select>

<div u:ignore>
<select unicorn:model="selected_state" class="form-control" id="select2-example" required onchange="Unicorn.call('js', 'select_state', this.value, this.selectedIndex);">
{% for state in states %}
<option value="{{ state }}">{{ state }}</option>
{% endfor %}
</select>
States (in ignored div): {{ states }}
</div>

States (in ignored div): {{ states }}
</div>
selected_state: {{ selected_state }}

selected_state: {{ selected_state }}
<script>
$(document).ready(function () {
$('#select2-example').select2();
});
</script>

<script>
$(document).ready(function() {
$('#select2-example').select2();
});
</script>
<div>
States (not in ignored div): {{ states }}<br />
<button type="submit" u:click="change_states">Change states</button>
</div>

<div>
<input type="text" u:model="select2_datetime" />
<button type="submit" u:click="get_now">Get now</button>
<div>
States (not in ignored div): {{ states }}<br />
<button type="submit" u:click="change_states">Change states</button>
select2_datetime: {{ select2_datetime }}
</div>
</div>

<div>
<input type="text" u:model="select2_datetime" />
<button type="submit" u:click="get_now">Get now</button>
<div>
select2_datetime: {{ select2_datetime }}
</div>
</div>
<div unicorn:key="visibility">
<span unicorn:visible.threshold-25.debounce-1000="increase_counter" unicorn:partial="visibility">
Number of times this span was scrolled into view: {{ scroll_counter }}
</span>
</div>
</div>
43 changes: 43 additions & 0 deletions tests/js/element/visibility.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import test from "ava";
import { getComponent, getElement } from "../utils.js";

test("visible", (t) => {
const html = "<span unicorn:visible='test_function1'></span>";
const element = getElement(html);

const { visibility } = element;
t.is(visibility.method, "test_function1");
t.is(visibility.threshold, 0);
t.is(visibility.debounceTime, 0);
});

test("visible threshold", (t) => {
const html = "<span unicorn:visible.threshold-25='test_function2'></span>";
const element = getElement(html);

const { visibility } = element;
t.is(visibility.method, "test_function2");
t.is(visibility.threshold, 0.25);
t.is(visibility.debounceTime, 0);
});

test("visible debounce", (t) => {
const html = "<span unicorn:visible.debounce-1000='test_function3'></span>";
const element = getElement(html);

const { visibility } = element;
t.is(visibility.method, "test_function3");
t.is(visibility.threshold, 0);
t.is(visibility.debounceTime, 1000);
});

test("visible chained", (t) => {
const html =
"<span unicorn:visible.threshold-50.debounce-2000='test_function4'></span>";
const element = getElement(html);

const { visibility } = element;
t.is(visibility.method, "test_function4");
t.is(visibility.threshold, 0.5);
t.is(visibility.debounceTime, 2000);
});

0 comments on commit 4406642

Please sign in to comment.