- Setup
- Log when a property or properties change with .log
- Break when a property changes
- Understand what caused a particular thing to happen with logStack
- Understand what caused a particular thing to happen with logStack's reasonLog
- Logging the state of the ViewModel
- Understanding what caused something to change with components and logStack
- Logging the scope
- Logging values in the scope
- Debugging the scope
- Understand what changes the DOM
- Understand what changes an observable
- In this section, we will make sure that we have access to the debugging in various environments:
- Using the
mjs
builds (CodePen). - Using StealJS individual builds.
- Using the
We want to be able to get this component working:
Component.extend({
tag: "my-counter",
view: `
Count: <span>{{this.count}}</span>
<button on:click='this.increment()'>+1</button>
`,
ViewModel: {
count: {default: 0},
increment() {
this.count++;
}
}
});
And then be able to see its ViewModel
in the CanJS ViewModel inspector:
We also want to be able to write can.debug
and access the debugger helpers:
- Install the Chrome Web Store Extension.
- Start a new CodePen
- Import
Component
from"https://unpkg.com/can/core.mjs"
. - Make sure to add
<my-counter></my-counter>
to the HTML.
mkdir can-steal
cd can-steal
npm init --yes
npm i steal can-component can-debug
npm i steal-conditional --save-dev
Configure steal-conditional
in package.json
{
"name": "can-steal",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"can-component": "^4.4.2",
"can-debug": "^2.0.1",
"steal": "^2.1.3",
"steal-conditional": "^1.1.1"
},
"steal": {
"configDependencies": [
"node_modules/steal-conditional/conditional"
]
}
}
dev.html
<my-counter></my-counter>
<script src="./node_modules/steal/steal.js" main></script>
index.js
import Component from "can-component";
import debug from "can-debug#?./is-dev";
debug();
Component.extend({
tag: "my-counter",
view: `
Count: <span>{{this.count}}</span>
<button on:click='this.increment()'>+1</button>
`,
ViewModel: {
count: {default: 0},
increment() {
this.count++;
}
}
});
is-dev.js
import steal from "@steal";
export default !steal.isEnv("production")
This CodePen changes confusing
's properties. The property names
write out a message.
Use .log()
to log when any property changes on an observable.
Use .log(key)
to log when a specific property changes on an observable.
Click to See
Add the following after confusing
is instantiated:
// Maybe listen to properties changing here:
confusing.log();
The properties changed write out all
you
need
is
love
.
We want to see what's changing a property.
This CodePen is randomly
changing propA
, propB
, propC
. When those change, final
is being incremented:
confusing.on("ticker", function(){
var props = ["propA","propB","propC"];
var prop = props[rand()];
confusing[prop]++;
},"domUI");
confusing.on("propA", function canjs(){
confusing.final++;
},"domUI");
confusing.on("propB", function stealjs(){
confusing.final++;
},"domUI");
confusing.on("propC", function donejs(){
confusing.final++;
},"domUI");
Which function (canjs
, stealjs
, or donejs
) is the one that is called when final
is incremented to 5?
If you simply listen to when a property changes like:
map.on("key", function(ev, newVal){
if(newVal === 5) {
debugger;
}
});
That handler will not be called immediately upon the change of the property. Instead, that handler will be added to the mutate
queue which fires at the end of a batch of changes.
If we do this with the CodePen:
confusing.on("final", function(ev, newVal){
if(newVal === 5) {
debugger;
}
});
We see this:
Instead, if you want to be immediately notified of a change, listen to the event in the "notify"
phase as follows:
map.on("prop", function handler(){
debugger;
}, "notify");
Click to See
Add the following to the CodePen:
confusing.on("final", function(ev, newVal){
if(newVal === 5) {
debugger;
}
},"notify");
The answer is stealjs
.
Figure out the properties that result in the final property changing in the following CodePen.
-
The call stack only tells you the first observable to change. Everything else you see are queues functions:
-
can-queues maintains a stack trace everything it does. CanJS does its best to give descriptive names to what is happening.
-
can.queues.logStack() prints this stack.
While debugging the final property changing in the following CodePen, you want to know what changed in message to cause the final change.
logStack
entries are really just function calls. Each entry gets logged with an object that incldues:
args
- The arguments passed to the functioncontext
- Thethis
of the functionfn
- The function that was calledmeta
- Additional information queues uses for debugging.
Critically, the meta
object also includes a reasonLog
. This is indented to be a human-readable explination of why that task was queued. CanJS provides it in development mode on most tasks.
There's a <some-state>
component on the page in this CodePen. Log its viewModel
's properties and values.
Components elements now have their viewModel
available as element.viewModel
. So use:
document.querySelector("some-component").viewModel
To get the ViewModel
and:
document.querySelector("some-component").viewModel.get()
To see it in an object form.
If you inspect the element, you can also use $0
to reference the last element you inspected:
$0.viewModel.get()
Click to See
Add:
console.log(document.querySelector('some-state').viewModel.get())
And you should see logged:
{
a: "viewModel",
property: "makes",
sense: "right"
}
- Understanding how component bindings (
foo:bind="bar"
) resulted in property updates can be confusing. - This CodePen's
<word-and>
'syou
property is changing as a result of several child components of<my-app>
passing around the value. - Can you trace how the value moved from one property to the next?
- Record the word in each "word" component's name and the property that was changed and it will spell a message.
- For example, if a component like
<word-hello>
'sworld
property changed, you would record "hello world".
- For example, if a component like
- There are 4 sub components whose properties changed.
When a binding updates a value, an entry like the following is added to the queue:
DOM_UI ran task: <a-component viewModelProp:bind="scopeKey"> updates <a-component>.viewModelProp from {{scopeKey}}
This means that scopeKey
changed and <a-component>.viewModelProp
was set to its value.
Bindings can also run the other way, so you might see:
DOM_UI ran task: <a-component viewModelProp:bind="scopeKey"> updates {{scopeKey}} from <a-component>.viewModelProp
This means <a-component>.viewModelProp
changed and scopeKey
was set to its value.
This Codepen has a class with a student with a missing parent name. Can you figure out which class and student has the missing parent name by exploring the scope?
You can call scope.log()
to log stache's scope.
If you don't want to do it all the time, it helps to do it conditionally:
HINT:
{{^ if() }}
can be used to inverse logic.
Click to See
Conditionally call scope.log()
:
{{# for(parent of student.parents) }}
{{^ if(parent.name) }} {{scope.log()}} {{/ if }}
<li>{{parent.name}}</li>
{{ /for}}
Then exploring the result will show the class is math
and the student is Rahim
:
This Codepen has a class with a student with a missing parent name. Can you figure out which class and student has the missing parent name by logging values in the scope?
You can use {{ console.log(key) }}
to log values in the scope.
Click to See
Use console.log()
:
{{# for(parent of student.parents) }}
{{console.log(class.name, student.name, parent.name)}}
<li>{{parent.name}}</li>
{{ /for}}
Then exploring the result will show the class is math
and the student is Rahim
:
This Codepen
has a class with a student with a missing (undefined
) parent name. Can you figure out
which class and student has the missing parent name by debugging the scope?
The CodePen uses the global build. Click to find out why.
There is a bug in the .mjs
builds. scope
and the get
function are being
dead code eliminated.
To read something from the scope:
arguments[2].scope.get("class").name
Break anytime this part of the template evaluates
{{debugger()}}
Break when condition is truthy
{{debugger(condition)}}
Break when left equals right
{{debugger(left, right)}}
This will break and give you access to a get
function that reads from the scope like:
get("class") //-> DefineMap{}
The scope itself is available as options.scope
:
options.scope.get("class") //-> DefineMap{}
PROTIP: If you have
stacheConverters
included, you could usenot()
like:{{ debugger( not(key) ) }}
Click to See
Use debugger(parent.name, undefined)
:
{{# for(parent of student.parents) }}
{{debugger(parent.name, undefined)}}
<li>{{parent.name}}</li>
{{ /for}}
Then exploring the result will show the class is reading
and the student is Adisa
:
This CodePen has an <h2>
element that reads a
whatChangesMe
element like:
<h2>What Changes Me? {{this.whatChangesMe}}</h2>
Can you figure out what properties of the <my-app>
ViewModel update the <h2>
element?
Use can.debug.logWhatChangesMe(element)
to log what changes an HTML element:
can.debug.logWhatChangesMe(element)
Click to See
Inspect the <h2>
element and run the following in the console:
can.debug.logWhatChangesMe($0)
This CodePen has a
<my-app>
's element with a first
and last
property. One of the 6 inputs changes the
first
property and one of the 6 inputs changes the last
property. Can you discover
those inputs without changing the inputs?
can.debug.logWhatChangesMe(observable [,key] )
will list out all the values (including elements)
that change a value.
Click to See
can.debug.logWhatChangesMe($0.viewModel, "first")
can.debug.logWhatChangesMe($0.viewModel, "last")
First is changed by Thomas
.
Last is changed by Paula
.
It would be nice if the CodePen's opened in a new tab by default