I have java script component in home component with external js. I need to remove external js when page navigate to another page. Page does not refresh.
<script>
function initFreshChat() {
window.fcWidget.init({
token: "***",
host: "https://wchat.freshchat.com"
});
}
function initialize(i,t){var e;i.getElementById(t)?initFreshChat():((e=i.createElement("script")).id=t,e.async=!0,e.src="https://wchat.freshchat.com/js/widget.js",e.onload=initFreshChat,i.head.appendChild(e))}function initiateCall(){initialize(document,"freshchat-js-sdk")}window.addEventListener?window.addEventListener("load",initiateCall,!1):window.attachEvent("load",initiateCall,!1);
</script>
This is the external js: https://wchat.freshchat.com/js/widget.js
I need this because i need to keep this freshchat window in one page.
This can be done by putting any condition. But it will works if we refresh the page. Here pages are not refreshing at all.
Therefore I need to remove the external js when navigate to another pages. And mount back when came to this page.
You can wrap the script in side a Vue component life circle,
render
remove
refresh
whenever you need.
I found this code on codepen https://codepen.io/akccakcctw/pen/LBKQZE
Vue.component("fc-button", {
template: "#fcButton",
props: {
fc: {
type: Object,
default: {},
}
},
methods: {
openWidget: function() {
document.getElementById("fc_frame").style.visibility = "visible";
window.fcWidget.open();
}
}
});
const vm = new Vue({
el: "#app",
data: function() {
return {
fc: {
isInit: false,
},
};
},
mounted: function() {
var self = this;
window.fcSettings = {
token: "8d3a4a04-5562-4f59-8f66-f84a269897a1",
host: "https://wchat.freshchat.com",
config: {
cssNames: {
widget: "custom_fc_frame",
open: "custom_fc_open",
expanded: "custom_fc_expanded"
},
headerProperty: {
hideChatButton: true
}
},
onInit: function() {
window.fcWidget.on("widget:loaded", function() {
self.fc.isInit = true;
window.fcWidget.on("unreadCount:notify", function(resp) {
console.log(resp);
test = resp;
if (resp.count > 0) {
// document.getElementById('notify').classList.add('h-btn-notify');
document.getElementById("notify").style.visibility = "visible";
} else if (resp.count == 0) {
// document.getElementById('notify').classList.remove('h-btn-notify');
document.getElementById("notify").style.visibility = "hidden";
}
});
window.fcWidget.on("widget:closed", function() {
document.getElementById("fc_frame").style.visibility = "hidden";
document.getElementById("open_fc_widget").style.visibility =
"visible";
});
window.fcWidget.on("widget:opened", function(resp) {
document.getElementById("open_fc_widget").style.visibility =
"hidden";
});
});
}
};
}
});
Related
I'm studying javascript and mithril.js 1.1.6. I'm writing down a simple web app in which users land on a page where he can login. Users who already did login land on a different page. I'm trying this using conditional routing, here is the main component:
const m = require("mithril");
...
import Eventbus from './whafodi/eventbus.js';
import WelcomePage from './ui/welcome.js';
import User from './model/user.js';
var eventbus = new Eventbus();
function MyApp() {
return {
usrAuth: function() {
m.route(document.body, "/", {
"/": { view: () => m("p", "hello")}
})
},
usrNotAuth: function() {
m.route(document.body, "/", {
"/": { render: v => m(WelcomePage, eventbus) }
})
},
oninit: function(vnode) {
vnode.state.user = new User();
eventbus.subscribe({
type: "login",
handle: function(action) {
vnode.state.user.token = action.token;
console.log(JSON.stringify(vnode.state.user));
}
});
},
view: function(vnode) {
if(vnode.state.user.token) {
this.usrAuth();
} else {
this.usrNotAuth();
}
}
}
};
m.mount(document.body, MyApp);
MyApp is the main component. It check if user has a token, then return the proper route. This is the component that is in charge to let users login:
const m = require("mithril");
const hellojs = require("hellojs");
function TopBar(node) {
var bus = node.attrs.eventbus;
function _login() {
hellojs('facebook').login({scope:'email'});
}
return {
oninit: function(vnode) {
hellojs.init({
facebook: XXXXXXX,
}, {
redirect_uri: 'http://localhost'
});
hellojs.on('auth.login', auth => {
var fbtoken = auth.authResponse.access_token;
m.request({
method:"POST",
url:"./myapp/login/fb/token",
data:auth.authResponse,
background: true
}).then(function(result){
console.log(result);
bus.publish({ type: "login", token: result.jwttoken });
m.route.set("/");
}, function(error){
console.log(error);
bus.publish({ type: "login", token: "" });
});
});
},
view: function(vnode) {
return m("div", [
m("button", { onclick: _login }, "Login")
]);
}
}
}
export default TopBar;
TopBar component occurs in the WelcomePage component mentioned in the main one. TopBar renders a button and use hello.js to login. It uses the EventBus bus parameter to tell main component user logged in (there is an handler in main component to update the user model). Once user logins, event is fired and main component updates the user model. Good. Now, how can trigger the main component to load the right route?
I read mithril'docs again and I found that RouteResolvers perfectly suit my needs. Here is an example:
var App = (function() {
var login;
function isLoggedIn(component) {
if(login) {
return component;
} else {
m.route.set("/hey");
}
}
return {
oninit: function(vnode) {
EventBus.subscribe({
type: "login",
handle: function(action) {
console.log("incoming action: " + JSON.stringify(action));
login = action.value;
}
});
},
oncreate: function(vnode) {
Foo.eventbus = EventBus;
Bar.eventbus = EventBus;
Hey.eventbus = EventBus;
m.route(document.body, "/hey", {
"/foo": {
onmatch: function(args, requestedPath, route) { return isLoggedIn(Foo); }
},
"/bar": {
onmatch: function(args, requestedPath, route) { return isLoggedIn(Bar); }
},
"/hey": Hey
});
},
view: function(vnode) {
return m("div", "home..");
}
};
})();
Eventbus is used to let components communicate with App. They fire events (login type events) that App can handle. I found convenient to pass Eventbus the way oncreate method shows, I can use Eventbus in each component's oncreate to let components fire events.
I want to add a test for my app which involves taking payment. In my apps local environment it uses a stubbed payment page where you only need to click a button to fail or authorise the payment, in all other environments it shows a form where card details need to be filled in.
I currently have the test setup to check whether or not we need to use the real or stubbed payment in each command.
function isRealPayment(page) {
return !page.api.globals.stubbedPayment;
}
module.exports = {
commands: {
verifyLoaded: function() {
if (isRealPayment(this)) {
return this.waitForElementVisible('#orderSummaryContainer');
}
return this.waitForElementVisible('#stubbedAuthorisedForm');
},
fillInPaymentDetails: function() {
if (isRealPayment(this)) {
this
.setValue('#cardNumber', '4444333322221111')
.setValue('#name', 'John Doe')
.setValue('#expiryMonth', '12')
.setValue('#expiryYear', '25')
.setValue('#securityCode', '123');
}
},
submitPayment: function() {
if (isRealPayment(this)) {
return this.click('#submitButton');
}
return this.click('#stubbedSubmitButton');
}
},
elements: {
orderSummaryContainer: '#orderSummaryDetailsTop',
cardNumber: '#cardNumber',
name: '#cardholderName',
expiryMonth: '#expiryMonth',
expiryYear: '#expiryYear',
securityCode: '#securityCode',
submitButton: '#submitButton',
stubbedAuthorisedForm: '.frm-AUTHORISED',
stubbedSubmitButton: '.frm-AUTHORISED > input[type="submit"]'
}
};
I would prefer it if I were able to define two different page objects, and choose which one to export based on the stubbedPayment global.
e.g
let realPaymentPage = {
commands: {
verifyLoaded: function() {
return this.waitForElementVisible('#orderSummaryContainer');
},
fillInPaymentDetails: function() {
this
.setValue('#cardNumber', '4444333322221111')
.setValue('#name', 'John Doe')
.setValue('#expiryMonth', '12')
.setValue('#expiryYear', '25')
.setValue('#securityCode', '123');
},
submitPayment: function() {
return this.click('#submitButton');
}
},
elements: {
orderSummaryContainer: '#orderSummaryDetailsTop',
cardNumber: '#cardNumber',
name: '#cardholderName',
expiryMonth: '#expiryMonth',
expiryYear: '#expiryYear',
securityCode: '#securityCode',
submitButton: '#submitButton'
}
};
let stubbedPaymentPage = {
commands: {
verifyLoaded: function() {
return this.waitForElementVisible('#authorisedForm');
},
fillInPaymentDetails: function() {
// Do nothing
},
submitPayment: function() {
return this.click('#submitButton');
}
},
elements: {
authorisedForm: '.frm-AUTHORISED',
submitButton: '.frm-AUTHORISED > input[type="submit"]'
}
};
if (browser.globals.stubbedPayment) {
module.exports = stubbedPaymentPage;
} else {
module.exports = realPaymentPage;
}
But I can't find a way to access the global variables when not in a page command. Is this possible? Or is there another way to load a different page object based on the test environment?
Sure you are. example solution:
Firstly lets create Global.js file.
Add path to the file inside nightwatch.json:
"globals_path": "Global.js"
In Global.js define before method (it is executed once before any of test):
var self = module.exports = {
environment: undefined,
before: function (done) {
// parseArgumentsAndGetEnv is function you need to implement on your own to find your env param
self.environment = parseArgumentsAndGetEnv(process.argv);
console.log("Run against: " + self.environment);
done();
}
};
Now in tests you can use this variable:
if (browser.globals.environment == 'Test') {
// do something
} else if (browser.globals.environment == 'Prod') {
// do something else
}
I am working on a site that uses Backbone.js, jQuery and I am trying to render a subview that has to display a description of the current page, loaded depending on a choice made from a dropdown menu. I searched for more info in the web but I am still stuck on this. Please help!
Here is the main view in which I have to load the description view:
const InquiryContentView = Backbone.View.extend(
{
el: $('#inquiryContent'),
events: {
'change #styles': 'renderTabs',
'click li.tab': 'renderTabPanel'
},
initialize: function () {
const view = this
this.inquiryContent = new InquiryContent
this.inquiryContent.fetch(
{
success: function () {
view.listenTo(view.inquiryContent, 'update', view.render)
view.render()
}
})
},
render: function () {
const data = []
this.inquiryContent.each(function (model) {
const value = {}
value.id = model.id
value.text = model.id
value.disabled = !model.get('active')
data.push(value)
})
data.unshift({id: 'none', text: 'Select One', disabled: true, selected: true})
this.$el.append('<h2 class="pageHeader">Inquiry Content</h2>')
this.$el.append('<select id="styles"></select>')
this.$stylesDropdown = $('#styles')
this.$stylesDropdown.select2(
{
data: data,
dropdownAutoWidth: true,
width: 'element',
minimumResultsForSearch: 10
}
)
this.$el.append('<div id="navWrapper"></div>')
this.$el.append('<div id="tNavigation"></div>')
this.$navWrapper = $('#navWrapper')
this.$tNavigation = $('#tNavigation')
this.$navWrapper.append(this.$tNavigation)
this.$el.append('<div id="editorDescription"></div>')
},
renderTabs: function (id) {
const style = this.inquiryContent.findWhere({id: id.currentTarget.value})
if (this.clearTabPanel()) {
this.clearTabs()
this.tabsView = new TabsView({style: style})
this.$tNavigation.append(this.tabsView.el)
}
},
renderTabPanel (e) {
const tabModel = this.tabsView.tabClicked(e.currentTarget.id)
if (tabModel && this.clearTabPanel()) {
this.tabPanel = new TabPanelView({model: tabModel})
this.$tNavigation.append(this.tabPanel.render().el)
}
},
clearTabs: function () {
if (this.tabsView !== undefined && this.tabsView !== null) {
this.tabsView.remove()
}
},
clearTabPanel: function () {
if (this.tabPanel !== undefined && this.tabPanel !== null) {
if (this.tabPanel.dataEditor !== undefined && this.tabPanel.dataEditor.unsavedChanges) {
if (!confirm('You have unsaved changes that will be lost if you leave the page. '
+ 'Are you sure you want to leave the page without saving your changes?')) {
return false
}
this.tabPanel.dataEditor.unsavedChanges = false
}
this.tabPanel.remove()
}
return true
}
}
)
I am trying to render the subview adding this method:
renderDescription () {
this.$editorDescription = $('#editorDescription')
this.descView = new DescView({model: this.model})
this.$editorDescription.append(this.descView)
this.$editorDescription.html(DescView.render().el)
}
It has to be rendered in a div element with id='editorDescription'
but I receive Uncaught ReferenceError: DescView is not defined
Here is how DescView is implemented:
window.DescView = Backbone.View.extend(
{
el: $('#editorDescription'),
initialize: function () {
_.bindAll(this, 'render')
this.render()
},
render: function () {
$('#editorDescriptionTemplate').tmpl(
{
description: this.model.get('description')})
.appendTo(this.el)
}
);
What am I doing wrong?
Your implementation of the DescView is incomplete. You are missing a bunch of closing braces and brackets. That's why it's undefined.
I am new to Backbone and have an issue from almous beginning wondering how to overcome issue with unexpected behavior of a main view.
1. I launch page and it looks okey.
2. I click button that lead me to different view and shows me it well.
3. I click "back" button and I see a blank page, but in DOM I can find some elements from previous view, but not visible.
Here is a piece of my router code (I hope this pieces will be enough):
home: function () {
if (!app.leftMenuView) {
app.leftMenuView = new app.views.LeftMenuView({
el: $("#left_menu")
});
} else {
app.leftMenuView.delegateEvents();
}
if (!app.homeView) {
app.homeView = new app.views.HomeView({
el: $("#main_container")
});
} else {
app.homeView.delegateEvents();
}
if (!app.topMenuView) {
app.topMenuView = new app.views.topMenuView({
el: $("#top_menu")
});
} else {
app.topMenuView.delegateEvents();
}
},
search: function () {
app.searchView = new app.views.SearchView({
el: $("body")
});
},
A piece of main html file:
<body>
<div id="search-div"></div>
...
</body>
HomeView:
app.views.HomeView = Backbone.View.extend({
initialize: function () {
this.render()
},
render: function () {
var self = this;
$.get('/template/home.html', function (data) {
self.$el.html(_.template(data)({}));
});
return this;
},
});
A SearchView:
app.views.SearchView = Backbone.View.extend({
events: {
"click #left-arrow-icon": "toMainPage"
},
initialize: function () {
this.render();
},
render: function () {
var self = this;
$.get('/template/searchView.html', function (data) {
self.$el.html(_.template(data));
});
return this;
},
toMainPage: function () {
Backbone.history.history.back();
},
});
In the example provided, the search view, if rendered, will destroy the main html file content since the element provided is directly the body. In any case, if you go to search view, your home page will have been destroyed or will not be able to render anymore.
I would suggest an improved layout management :
separate elements per views
show/hide of root elements based on routes
I've just started foundational work for a Backbone JS SPA (single page application). I'm using the basic Underscore templating support, and am having issues with unexpected routing occurring.
Basically, the sign up view is shown initially as expected, POSTs succesfully when I click a button and I have it navigate to a simple test view. However, the test view is quickly rendered and then I get re-routed to the default sign up view again.
I see the history of the test page and if I hit the back button I go back to that test view which works fine. I see there is some event being triggered in Backbone which is routing me back to the blank fragment (sign up page), but I have no idea why. I've tried messing with the replace and trigger options on the navigate call with no luck.
As a side note, the start() and stop() View functions were adapted from this article: http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/ . I tried removing this and it had no effect.
$(document).ready( function(){
Backbone.View.prototype.start = function() {
console.debug('starting');
if (this.model && this.modelEvents) {
_.each(this.modelEvents,
function(functionName, event) {
this.model.bind(event, this[functionName], this);
}, this);
}
console.debug('started');
return this;
};
Backbone.View.prototype.stop = function() {
console.debug('stopping');
if (this.model && this.modelEvents) {
_.each(this.modelEvents,
function(functionName, event) {
this.model.unbind(event, this[functionName]);
}, this);
}
console.debug('stopped');
return this;
};
var myApp = {};
myApp.SignUp = Backbone.Model.extend({
urlRoot:"rest/v1/user",
defaults: {
emailAddress: "email#me.com",
firstName: "First Name",
lastName: "Last Name",
password: "",
confirmPassword: ""
}
});
myApp.SignUpView = Backbone.View.extend({
el: $('#bodyTarget'),
modelEvents: {
'change' : 'render'
},
render: function(){
document.title = "Sign Up Page";
// Compile the template using underscore
var template = _.template( $("#signUpTemplate").html(), this.model.toJSON());
// Load the compiled HTML into the Backbone "el"
this.$el.html( template );
return this;
},
events: {
"click .signUpButton": "signUp"
},
signUp: function() {
bindFormValues(this);
this.model.save(this.model.attributes, {error: this.signUpFailure});
myApp.router.navigate("test", {trigger: true});
},
signUpFailure: function(model, response) {
alert("Failure: " + response);
}
});
myApp.TestView = Backbone.View.extend({
el: $('#bodyTarget'),
modelEvents: {
'change' : 'render'
},
render: function() {
document.title = "Test Page";
this.$el.html( "<div>this is a test view</div>");
return this;
}
});
// for now, just pull values from form back into model
function bindFormValues(view) {
var mod = view.model;
var el = view.$el;
var updates = {};
for (var prop in mod.attributes) {
var found = el.find('* [name="' + prop + '"]');
if (found.length > 0) {
updates[prop] = found.val();
}
}
mod.set(updates/*, {error: errorHandler}*/);
}
// routers
myApp.Router = Backbone.Router.extend({
routes: {
'test': 'test',
'': 'home',
},
home: function() {
console.debug('home:enter');
this.signUpView = new myApp.SignUpView({model: new myApp.SignUp()});
this.showView(this.signUpView);
console.debug('home:exit');
},
test: function() {
console.debug('test:enter');
this.testView = new myApp.TestView({model: new myApp.SignUp()});
this.showView(this.testView);
console.debug('test:exit');
},
showView: function(view) {
if (this.currentView) {
this.currentView.stop();
}
this.currentView = view.start().render();
}
});
myApp.router = new myApp.Router();
Backbone.history.start();
});
My HTML page just brings in the relevant scripts and has the div element bodyTarget which is injected with the views when it loads.
Edit: Duh, I found the problem. It turns out I needed to prevent event propagation on the call to signUp() by returning false.