Unnamed Fossil Project

Check-in [6a9d6c9f50]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Start bringing up item clicking and transition
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256:6a9d6c9f50722d12f037171cee8a5e403dd013ac8e631e2e1869e766c7d081a8
User & Date: vandys 2019-06-21 17:03:35
Context
2019-06-21
23:55
Get item changes and updating with visual cues working. check-in: 470fc733db user: vandys tags: trunk
17:03
Start bringing up item clicking and transition check-in: 6a9d6c9f50 user: vandys tags: trunk
16:29
Tidy up top line check-in: 07484c5ab0 user: vandys tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to html/shopr.html.

1
2
3
4
5
6
7

8
9
10
11
12
13
14
<html>
<head>
<title>
shopr
</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/css/shopr.css">

<script src="/js/shopr.js"></script>
</head>
<body>

<div id="scrlogin" style="display: none;">
 <input type="text" id="user" placeholder="User name">
 <br>







>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html>
<head>
<title>
shopr
</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/css/shopr.css">
<script src="/js/clicker.js"></script>
<script src="/js/shopr.js"></script>
</head>
<body>

<div id="scrlogin" style="display: none;">
 <input type="text" id="user" placeholder="User name">
 <br>

Added imgs/menu.svg.

























































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?xml version="1.0" ?>
<svg
 height="32px"
 version="1.1"
 viewBox="0 0 16 16"
 width="32px"
 xmlns="http://www.w3.org/2000/svg"
 xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"
 xmlns:xlink="http://www.w3.org/1999/xlink"><title/><desc/><defs/>
 <g
  fill="none"
  fill-rule="evenodd"
  id="Page-1"
  stroke="none"
  stroke-width="1">
  <g
   fill="#000000"
   id="Core"
   transform="translate(-87.000000, -342.000000)">
   <g
    id="menu"
    transform="translate(87.000000, 342.000000)">
    <path d="M0,12 L18,12 L18,10 L0,10 L0,12 L0,12 Z M0,7 L18,7 L18,5 L0,5 L0,7 L0,7 Z M0,0 L0,2 L18,2 L18,0 L0,0 L0,0 Z" id="Shape"
    fill="black" />
   </g>
  </g>
 </g>
</svg>

Added js/clicker.js.































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
//
// clicker.js
//	Factor out all the tedium of click/hold
//
// Copyright 2018 by Andy Valencia, all rights reserved
//

// Milliseconds to count as a long hold
const LONGHOLD = 1000;

// Units of scroll before it's "real".
//
// On touch, the display may wiggle a bit while you're trying to
//  click, so try to give a brother a break
const SCROLLDX = 15;

// Return [n]umerator over [d]enominator as a percentage
//  0..100
function genfrac(n, d) {
    n = parseFloat(n);
    d = parseFloat(d);
    return Math.floor((n / d) * 100);
}

class Clicker {
    // Watch @elem, invoking @click and @hold when we
    //  see their respective events.
    constructor(elem, click, hold) {
	this.elem = elem;
	this.click = click;
	this.hold = hold;

	// For telling click versus hold
	this.timer = null;

	// Percent of X/Y of initial contact
	this.target = null;
	this.px = this.py = null;

	// On touch, need to distinguish scrolling from a true
	//  "click"
	this.sstart = this.sdx = null;

	elem.onmousedown = this.mousedown.bind(this);
	elem.ontouchstart = this.touchstart.bind(this);
    }

    // General cleanup
    cleanup() {
	if (this.timer != null) {
	    clearTimeout(this.timer);
	    this.timer = null;
	}
	this.target = null;
	this.px = this.py = null;
	this.sstart = this.sdx = null;
	this.elem.onmouseup = this.elem.onmouseleave = null;
	this.elem.ontouchend = this.elem.ontouchleave = null;
    }

    // Long hold
    timeout() {
	if (!this.we_scrolled() && (this.hold != null)) {
	    this.hold(this.target, this.px, this.py);
	}
	this.cleanup();
    }

    // Mouse/touch came back up
    gotclick() {
	if (!this.we_scrolled() && (this.click != null)) {
	    this.click(this.target, this.px, this.py);
	}
	this.cleanup();
    }

    // Mouse click down
    mousedown(e) {
	this.cleanup();
	
	// No longer bother wondering about a touchscreen
	this.elem.ontouchstart = null;

	// Note landing spot
	const t = e.target;
	this.target = t;
	this.px = genfrac(e.offsetX, t.clientWidth);
	this.py = genfrac(e.offsetY, t.clientHeight);

	// Mouse handlers
	this.elem.onmouseup = this.gotclick.bind(this);
	this.elem.onmouseleave = this.cleanup.bind(this);

	// Wait for up/timeout/leave
	this.timer = setTimeout(this.timeout.bind(this), LONGHOLD);
    }

    // Tell if our user did enough motion that this is just
    //  them scrolling, and a click/hold should not result
    we_scrolled() {
	if (this.sstart == null) {
	    return false;
	}
	return this.sdx >= SCROLLDX;
    }

    // We've seen the display scrolling
    //
    // If it scrolls "enough", we consider it true scrolling
    //  and don't emit a click or long-hold event.  We just
    //  let them scroll around, raise their finger, and then
    //  they can click/hold when their finger comes down again.
    scrolling() {
	if (this.sstart == null) {
	    this.sstart = this.elem.scrollTop;
	    this.sdx = 0;
	    return;
	}
	this.sdx = Math.max(this.sdx,
	    Math.abs(this.sstart - this.elem.scrollTop));
    }

    // Touch screen down
    touchstart(e) {
	this.cleanup();

	// Touchscreen; don't worry about the mouse
	this.elem.onmousedown = null;

	// Touch position
	const tch = e.changedTouches[0];

	// Adjust for position in screen
	const targ = tch.target;
	this.target = targ;
	let offx = 0, offy = 0;
	for (let elem = targ; elem != null;
		elem = elem.offsetParent) {
	    offx += elem.offsetLeft;
	    offy += elem.offsetTop;
	}

	// Normalize X/Y to float percent 0..1 of width/height
	let px = (parseFloat(tch.screenX) - parseFloat(offx));
	px /= parseFloat(targ.clientWidth);
	this.px = Math.min(1.0, Math.max(0.0, px));
	let py = (parseFloat(tch.screenY) - parseFloat(offy));
	px /= parseFloat(targ.clientHeight);
	this.py = Math.min(1.0, Math.max(0.0, py));

	// Touch handlers
	this.elem.ontouchend = this.gotclick.bind(this);
	this.elem.ontouchleave = this.cleanup.bind(this);
	this.elem.onscroll = this.scrolling.bind(this);

	// Wait for touch release
	this.timer = setTimeout(this.timeout.bind(this), LONGHOLD);
    }
}

Changes to js/shopr.js.

40
41
42
43
44
45
46












































































47
48
49
50
51
52
53
54
55
56
57
58
59



60
61

62
63

64
65

66
67
68
69
70
71
72
..
89
90
91
92
93
94
95




96
97
98
99
100
101
102
...
172
173
174
175
176
177
178



179
180
181
182
183
184
185
	return;
    }
    localStorage.setItem("user", user.value);
    localStorage.setItem("password", password.value);
    calc_authURL();
    get_lists();
}













































































// Paint server's description of list elements
function show_list(ls) {
    // Clear old list display
    while (items.firstChild != null) {
	items.removeChild(items.firstChild);
    }

    // Display received items
    for (let i = 0; i < ls.items.length; ++i) {
	const l = ls.items[i];

	// Red/Green/Yellow tappables



	const s1 = document.createElement("span");
	s1.onclick = () => item_click(i, 0);

	const s2 = document.createElement("span");
	s2.onclick = () => item_click(i, 1);

	const s3 = document.createElement("span");
	s3.onclick = () => item_click(i, 2);


	// The text goes in the one selected
	if (l.idx == 0) {
	    s1.textContent = l.name;
	    s1.classList.add("isel");
	} else if (l.idx == 1) {
	    s2.textContent = l.name;
................................................................................
// Display the current list
function paint_list() {
    const req = new XMLHttpRequest();
    req.onreadystatechange = () => {
	if (req.readyState != 4) {
	    return;
	}




	if (req.status != 200) {
	    if (req.status == 403) {
		// Changed, password, deleted account, ???
		setcur(scrlogin);
		alert("Login credentials failed, please try again");
	    } else {
		alert("Failed to get list " + cur_list);
................................................................................
    // Requesting URL, including authentication
    req.open("GET", "/lists.json" + authURL);
    req.send();
}

// Initial load of shopr app page
function init_page() {



    // Saved values?
    const u = localStorage.getItem("user");
    if (u != null) {
	uname = user.value = u;
    }
    const p = localStorage.getItem("password");
    if (p != null) {







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>













>
>
>

<
>

<
>

<
>







 







>
>
>
>







 







>
>
>







40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139

140
141

142
143

144
145
146
147
148
149
150
151
...
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
...
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
	return;
    }
    localStorage.setItem("user", user.value);
    localStorage.setItem("password", password.value);
    calc_authURL();
    get_lists();
}

// Highlighting pending changes
var cur_bright = null, tmo_bright = null;

// Didn't work out, reset elem and complain
function cancel_bright() {
    tmo_bright = null;

    // But then how'd we get here?
    if (cur_bright == null) {
	return;
    }

    // Clear elem local styling
    cur_bright.style.border = "";
    cur_bright = null;

    // Wah
    alert("Item update: can't reach server");
}

// Server update came in OK
function ok_bright() {
    if (cur_bright == null) {
	// Nothing pending (somebody else tapped a button, not us)
	return;
    }

    // We're OK now
    clearTimeout(tmo_bright);

    // Stop highlighting
    cur_bright.style.border = "";

    // All state cleared
    cur_bright = tmo_bright = null;
}

// Set the outline bright white
//
// This can clear when we get a server update, or else
//  after a timeout we clear it ourselves and warn that
//  the server apparently never heard about the change.
function set_bright(e) {
    e.style.border = "6px solid white";
    cur_bright = e;
    tmo_bright = setTimeout(cancel_bright, 3000);
}

// Long hold; mostly no-op, menu on current element
function hold_item(e) {
    console.log("TBD: hold_item");
}

// Click an item, probably switch the index of the item
function click_item(e) {
    // Tap on already selected item index?
    if (e.textContent) {
	return false;
    }

    // If change already pending, ignore them
    if (cur_bright != null) {
	return false;
    }

    // The "id" of the element encodes its row/col
    //  "item<row>,<col>"
    const tup = e.id.substring(4).split('_');
    const row = parseInt(tup[0]), col = parseInt(tup[1]);

    // We don't switch the item; we flag it as having a change
    //  on the way, and tell the server.  It'll wake up our XHR
    //  and let us update that way.
    set_bright(e);
}

// Paint server's description of list elements
function show_list(ls) {
    // Clear old list display
    while (items.firstChild != null) {
	items.removeChild(items.firstChild);
    }

    // Display received items
    for (let i = 0; i < ls.items.length; ++i) {
	const l = ls.items[i];

	// Red/Green/Yellow tappables
	// The element "id" is item<row>_<col> so click
	//  handlers can quickly figure out which one
	//  was tapped.
	const s1 = document.createElement("span");

	s1.id = "item" + i.toString() + "_0";
	const s2 = document.createElement("span");

	s2.id = "item" + i.toString() + "_1";
	const s3 = document.createElement("span");

	s3.id = "item" + i.toString() + "_2";

	// The text goes in the one selected
	if (l.idx == 0) {
	    s1.textContent = l.name;
	    s1.classList.add("isel");
	} else if (l.idx == 1) {
	    s2.textContent = l.name;
................................................................................
// Display the current list
function paint_list() {
    const req = new XMLHttpRequest();
    req.onreadystatechange = () => {
	if (req.readyState != 4) {
	    return;
	}

	// Pending display op now resolved
	ok_bright();

	if (req.status != 200) {
	    if (req.status == 403) {
		// Changed, password, deleted account, ???
		setcur(scrlogin);
		alert("Login credentials failed, please try again");
	    } else {
		alert("Failed to get list " + cur_list);
................................................................................
    // Requesting URL, including authentication
    req.open("GET", "/lists.json" + authURL);
    req.send();
}

// Initial load of shopr app page
function init_page() {
    // Manage click/long-hold of item list
    const c = new Clicker(items, click_item, hold_item);

    // Saved values?
    const u = localStorage.getItem("user");
    if (u != null) {
	uname = user.value = u;
    }
    const p = localStorage.getItem("password");
    if (p != null) {