I can't get UserFlow to work for our AngularJS app.
The product runs on old AngularJS (1.8) and we love the concept of UserFlow , but the typical injection and init model runs in the core JS scope which AngularJS does not have access to... so, even after following the onboarding instructions, every user that registers is appearing to UserFlow as the same {{userId}}
We believe this is happening (UserFlow is not able to receive the user ID in userflow.identify as described here) because the user ID is not known outside of the AngularJS digest. i.e. - the method was called in a place where angularJS is not taking effect, so the handlebars never get rewritten.
Got it fixed. An overview of how we fixed it is below:
Simply split UserFlow's initialization into two distinct steps:
userflow.init() - this can be directly in your index.html or otherwise injected into the <body>
userflow.identify() - this has to be done within your AngularJS controller
.
DETAILED STEPS---------------
1. init() normally, but use a build variable and don't identify yet
In index.html, at the bottom of the <body> tag, add the following script:
<!-- UserFlow -->
<script ng-if="customization.features.userflow">
!function(){var e="undefined"==typeof window?{}:window,t=e.userflow;if(!t){var r="https://js.userflow.com/";t=e.userflow={_stubbed:!0};var n=e.USERFLOWJS_QUEUE=e.USERFLOWJS_QUEUE||[],o=function(e){t[e]=function(){var t=Array.prototype.slice.call(arguments);i(),n.push([e,null,t])}},s=function(e){t[e]=function(){var t,r=Array.prototype.slice.call(arguments);i();var o=new Promise((function(e,r){t={resolve:e,reject:r}}));return n.push([e,t,r]),o}},a=function(e,r){t[e]=function(){return r}},u=!1,i=function(){if(!u){u=!0;var t=document.createElement("script");t.async=!0;var n=e.USERFLOWJS_ENV_VARS||{};"es2020"===(n.USERFLOWJS_BROWSER_TARGET||function(e){for(var t=[[/Edg\//,/Edg\/(\d+)/,80],[/OPR\//,/OPR\/(\d+)/,67],[/Chrome\//,/Chrome\/(\d+)/,80],[/Safari\//,/Version\/(\d+)/,14],[/Firefox\//,/Firefox\/(\d+)/,74]],r=0;r<t.length;r++){var n=t[r],o=n[0],s=n[1],a=n[2];if(e.match(o)){var u=e.match(new RegExp(s));if(u&&parseInt(u[1],10)>=a)return"es2020";break}}return"legacy"}(navigator.userAgent))?(t.type="module",t.src=n.USERFLOWJS_ES2020_URL||r+"es2020/userflow.js"):t.src=n.USERFLOWJS_LEGACY_URL||r+"legacy/userflow.js",t.onerror=function(){u=!1,console.error("Could not load Userflow.js")},document.head.appendChild(t)}};o("_setTargetEnv"),o("closeResourceCenter"),o("init"),o("off"),o("on"),o("prepareAudio"),o("registerCustomInput"),o("remount"),o("reset"),o("setCustomInputSelector"),o("setCustomNavigate"),o("setCustomScrollIntoView"),o("setInferenceAttributeFilter"),o("setInferenceAttributeNames"),o("setInferenceClassNameFilter"),o("setResourceCenterLauncherHidden"),o("setScrollPadding"),o("setShadowDomEnabled"),o("setPageTrackingDisabled"),o("setUrlFilter"),o("openResourceCenter"),o("toggleResourceCenter"),s("endAll"),s("endAllFlows"),s("endChecklist"),s("group"),s("identify"),s("identifyAnonymous"),s("start"),s("startFlow"),s("startWalk"),s("track"),s("updateGroup"),s("updateUser"),a("getResourceCenterState",null),a("isIdentified",!1)}}();
userflow.init('##grunt_userflow')
</script>
Since we use Grunt as a build tool (which I don't recommend, but you can replicate the same pattern with different technologies), we put the environment-specific token, ##grunt_userflow, into our build script which replaces the individual token to match the respective environment.
You'll notice here we're not calling userflow.identify() yet...
2. Execute the UserFlow identify() directly within the controller
When the user first logs in, now you need to execute the userflow.identify() function and pass in the right IDs. Personally, I like putting AngularJS-agnostic functions like this outside of the controller and then inherit them in:
const startUserFlow = function(userId, login) {
userflow.identify(userId, {
email: login
});
};
And, now calling that function from within AJS:
$scope.processCredentials($scope.username, response.data.access_token).then(function (result) {
trackEvent('signIn', $rootScope.userProfile.id);
startUserFlow($rootScope.userProfile.id, $scope.username);
3. Finally, to reinitialize your Content, use ng-click=() on any HTML you'd like
That's right - since we're scoping it in and doing this the AngularJS way, use ng-click like any other function and bind it directly. Example below.
$scope.launchUserFlowChecklist = function () {
userflow.start('[insert content ID here]');
};
I hope this helps! Cheers.
I would like to do something like this:
<a <myDir myVar="someInput"></myDir>>Call myDir</A>
However I'm realising you can't nest a directive within an a tag and set parameters this way.
I'm actually using jade though. I call the directive fine with.
a(myDir) Call myDir
but am unsure how to pass the variable to the directive. I've tried the equivalent jade to the above html, which is this:
a(myDir(myVar="someInput")) Call myDir
As its an optional parameter I use the '=?' syntax in the directive.
However the issue is definitely the syntax for calling the function in jade.
So after much failure this is how you do it.
// In the HTML/Jade
a(myDir info="someInput")
// In the Directive
scope: {
info: '#?info'
}
Check out this question and #aifarfa's answer for an example. Though the question is different it's a perfect example of what I wanted to do.
How to call directive from template html in angularjs
I have one function in a JS file which should ideally be used by multiple html pages. I don't want to duplicate this function to another file. Currently, my js file starts like this:
(function(){
var app = angular.module('Project1', []);
and the first html that has been using this JS is obviously called Project1.
I want 'Prject2' html to use this JS to, and I tried this:
(function(){
var app = angular.module('Project1', 'Project2' []);
However, this doesn't work. Any idea of how I can utilize this AngularJS file for multiple html pages without duplicating the desired functions?
simply add your js reference (and angular refrences) into both of your html pages
do note that add them after adding angularjs references
You can create your first module as you did in your first sample.
Your next sample can then use the 'Project1' by setting up a dependency to that module, like so:
var app = angular.module('Project2', ['Project1']);
I am binding my typeahead for users images to hogan template and the code actually works fine but I am getting errors in console as it tries to get a resource: localhost:####/%7B%7BuserBlankImgUrl%7D%7D
The only thing that make sense is that it is trying to bind to the template value instead of waiting for a value.
Full disclosure: My app is using DurandalJS and knockoutJS pretty heavily but I tried to just show the relevant code below. I will include more based on suggestions.
Template:
<img class="quarc-avatar-list-item" src="{{userBlankImgUrl}}" />
JS:
self.userImgUrl = ko.computed(function () {
return avatar.fromGravatar(self.email(), self.gender());
});
Other things I have tried include:
Wrapping the template in null checks so doesn't try and bind.
When I remove the template hmtl I don't get the resource error in my browser console
Tried to learn more hogan and typeahead options to look for other options. Not sure if something like a pre-render or setting default "local" values would help?
Thanks,
-Chris
The first thing I notice is your self.userImgUrl is not the same userBlankImgUrl used in your template - if this is intentional it's not clear from your posted snippets.
However, at some point your scripts are outputting the template without running it through your template object's render() method. The bad request is caused by the src attribute for the/an image tag litterally being "{{userBlankImgUrl}}". I'm guessing you're testing your app on a local server, so the address it looks up is http://localhost/.../{{userBlankImgUrl}} (unless the ### in your question isn't just you shortening/hiding the path and it really is requesting from localhost:####/%7B%7BuserBlankImgUrl%7D%7D - either way the problem (or at least part of it) is the same).
Hogan.js removes any undefined handlebar'd variables that appear in templates it renders:
var template = Hogan.compile(
'<img class="quarc-avatar-list-item" src="{{undefinedVariable}}" />'
);
console.log( template.render({var1: "Test", var2: "Again"}) );
Will output to console:
<img class="quarc-avatar-list-item" src="" />
If anything appears in the src attribute or in the place of any {{templateVar}} where a null or undefined value was given then you know your template was outputted without going through the proper render method.
I am discovering angularJS, but I get an error Argument 'LawContrl' is not a function, got undefined. In my form, I have in my rails app
%div{"ng-controller" => "LawContrl"}
=form-for ...
%li{"ng-repeat"=>"entry in entries"} {{entry.name}} //I am using haml file
in my laws.js.coffee file, I have
#LawContrl = ($scope) ->
$scope.entries = [
{ name: "hi"}
{name: "ho"}
]
Ok, I think I got it. I don't know what ide you're using, but rename (or refactor then rename if you're using Eclipse or Rubymine) your application.js.coffee to application.js and everything must be working just fine. Btw, you should declare some LawContrl module in your angularJS so the controller can be passed to the view
Not sure what it could be, some things to check that might help with debugging this:
angular.js library script is included only once in the page
LawContrl script is included in the page after angular.js
ng-app attribute (it can be empty) is present in the HTML
If ng-app has a value, e.g. ng-app="foo", then LawContrl should be part of the foo module