I have drift's async script code in the index.html file of the react app.
<script>
"use strict";
!function () {
var t = window.driftt = window.drift = window.driftt || [];
if (!t.init) {
if (t.invoked) return void (window.console && console.error && console.error("Drift snippet included twice."));
t.invoked = !0, t.methods = ["identify", "config", "track", "reset", "debug", "show", "ping", "page", "hide", "off", "on"],
t.factory = function (e) {
return function () {
var n = Array.prototype.slice.call(arguments);
return n.unshift(e), t.push(n), t;
};
}, t.methods.forEach(function (e) {
t[e] = t.factory(e);
}), t.load = function (t) {
var e = 3e5, n = Math.ceil(new Date() / e) * e, o = document.createElement("script");
o.type = "text/javascript", o.async = !0, o.crossorigin = "anonymous", o.src = "https://js.driftt.com/include/" + n + "/" + t + ".js";
var i = document.getElementsByTagName("script")[0];
i.parentNode.insertBefore(o, i);
};
}
}();
drift.SNIPPET_VERSION = '0.3.1';
drift.load('----api----');
drift.on('ready', api => {
api.widget.hide();
})
</script>
The issue is, it is getting popped up in every page of the app whereas I want it only when I click a button(onClick)
The function to trigger onClick :
openDriftChat = () =>{
const { setDriftState } = this.props;
if (window.drift.api) {
//this needs to happen only once but currently happening on every page load
if (!this.props.driftInit) {
if (localStorage.token) {
var tokenBase64 = localStorage.token.split(".")[1];
var tokenBase64_1 = tokenBase64.replace("-", "+").replace("_", "/");
var token = JSON.parse(window.atob(tokenBase64_1));
window.drift.identify(token.email, {
email: token.email,
nickname: token.name
});
setDriftState(true);
}
}
window.drift.api.openChat();
}
}
I basically want it pop up only when I call the function.
Hello I had the same issue:
To hide the welcome message use the following css code
iframe#drift-widget.drift-widget-welcome-expanded-online {
display: none !important;
}
iframe#drift-widget.drift-widget-welcome-expanded-away {
display: none !important;
}
The welcome message will only be shown when your drift button. Some extra info:
To hide the drift button icon use the following js code
drift.on('ready', function (api) {
api.widget.hide()
drift.on('message', function (e) {
if (!e.data.sidebarOpen) {
api.widget.show()
}
})
drift.on('sidebarClose', function (e) {
if (e.data.widgetVisible) {
api.widget.hide()
}
})
})
To call for the sidebar from a specific button use the following
Javascript
(function () {
var DRIFT_CHAT_SELECTOR = '.drift-open-chat'
function ready(fn) {
if (document.readyState != 'loading') {
fn();
} else if (document.addEventListener) {
document.addEventListener('DOMContentLoaded', fn);
} else {
document.attachEvent('onreadystatechange', function () {
if (document.readyState != 'loading')
fn();
});
}
}
function forEachElement(selector, fn) {
var elements = document.querySelectorAll(selector);
for (var i = 0; i < elements.length; i++)
fn(elements[i], i);
}
function openSidebar(driftApi, event) {
event.preventDefault();
driftApi.sidebar.open();
return false;
}
ready(function () {
drift.on('ready', function (api) {
var handleClick = openSidebar.bind(this, api)
forEachElement(DRIFT_CHAT_SELECTOR, function (el) {
el.addEventListener('click', handleClick);
});
});
});
})();
HTML
<a class="drift-open-chat">Open Chat</a>
I hope this helps someone out there.
PS: The above javascript code must be included after you have initialized your drift widget.
You need to disable that through the application: turn off the Playbooks.
Here is the link to do so: https://app.drift.com/playbooks
Hope it helps.
I am trying to unmounted the react component when there is an entity change, for that I am using rerenderSearchPanel function in my angularJS project and then trying to mount it with initializeSearchPanel function.
Inside rerenderSearchPanel I have following code
searchElement && ReactDOM.unmountComponentAtNode(searchElement);
but after executing this line still the component exist its structure.
function rerenderSearchPanel(selectedEntity, savedChips) {
var searchElement = document.getElementById("searchPanel");
searchElement && ReactDOM.unmountComponentAtNode(searchElement);
$scope.initializeSearchPanel(selectedEntity, savedChips);
}
$scope.initializeSearchPanel = function (selectedEntity, savedChips) {
var searchElement = document.getElementById("searchPanel");
var searchDataSource = {
method: "GET",
catalogAPI: $scope.metaRestUrl + "specifications?
q=specName;EQUALS;EntityAttribute&q=subType;EQUALS;English",
searchAPI: $rootScope.smRestUrl + "graphSearch?limit=100&page=1",
selectedEntity: (selectedEntity ? selectedEntity :
$scope.currentEntity),
};
requestConfig.headers['TransformationEnabled'] = true;
searchDataSource.headers = requestConfig.headers
var options = {
searchProps: {
id: "searchInput1",
placeHolder: "Search",
editable: true,
},
chips: savedChips,
onTokenChange: $scope.search,
dataSource: searchDataSource
}
var search = React.createElement(SearchComponent, options);
searchElement && ReactDOM.render(search, searchElement);
}
But ReactDOM.unmountComponentAtNode is not unmounting.
I am learning AngularJS and I have a conventional JS Script that I wrote a while ago and I would like to use it inside my new Angular app.
Can I literally just dump the entire script in side the directive or do I need to change some things like the keyword this to element etc...?
directive.directive("skillLevel", ['$timeout', function($timeout) {
return{
link: function(scope, el, atts){
// CAN I PASTE MY SCRIPT HERE??
}
}]);
I have this 'quite length some' script that I want to use. How would I go about effectivly using this inside my directive?
(function ($) {
'use strict';
var RSS = function (target, url, options, callback) {
this.target = target;
this.url = url;
this.html = [];
this.effectQueue = [];
this.options = $.extend({
ssl: false,
host: 'www.feedrapp.info',
limit: null,
key: null,
layoutTemplate: '<ul>{entries}</ul>',
entryTemplate: '<li>[{author}#{date}] {title}<br/>{shortBodyPlain}</li>',
tokens: {},
outputMode: 'json',
dateFormat: 'dddd MMM Do',
dateLocale: 'en',
effect: 'show',
offsetStart: false,
offsetEnd: false,
error: function () {
console.log('jQuery RSS: url doesn\'t link to RSS-Feed');
},
onData: function () {},
success: function () {}
}, options || {});
// The current SSL certificate is only valid for *.herokuapp.com
if (this.options.ssl && (this.options.host === 'www.feedrapp.info')) {
this.options.host = 'feedrapp.herokuapp.com';
}
this.callback = callback || this.options.success;
};
RSS.htmlTags = [
'doctype', 'html', 'head', 'title', 'base', 'link', 'meta', 'style', 'script', 'noscript',
'body', 'article', 'nav', 'aside', 'section', 'header', 'footer', 'h1-h6', 'hgroup', 'address',
'p', 'hr', 'pre', 'blockquote', 'ol', 'ul', 'li', 'dl', 'dt', 'dd', 'figure', 'figcaption',
'div', 'table', 'caption', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td', 'col', 'colgroup',
'form', 'fieldset', 'legend', 'label', 'input', 'button', 'select', 'datalist', 'optgroup',
'option', 'textarea', 'keygen', 'output', 'progress', 'meter', 'details', 'summary', 'command',
'menu', 'del', 'ins', 'img', 'iframe', 'embed', 'object', 'param', 'video', 'audio', 'source',
'canvas', 'track', 'map', 'area', 'a', 'em', 'strong', 'i', 'b', 'u', 's', 'small', 'abbr', 'q',
'cite', 'dfn', 'sub', 'sup', 'time', 'code', 'kbd', 'samp', 'var', 'mark', 'bdi', 'bdo', 'ruby',
'rt', 'rp', 'span', 'br', 'wbr'
];
RSS.prototype.load = function (callback) {
var apiProtocol = 'http' + (this.options.ssl ? 's' : '');
var apiHost = apiProtocol + '://' + this.options.host;
var apiUrl = apiHost + '?callback=?&q=' + encodeURIComponent(this.url);
// set limit to offsetEnd if offset has been set
if (this.options.offsetStart && this.options.offsetEnd) {
this.options.limit = this.options.offsetEnd;
}
if (this.options.limit !== null) {
apiUrl += '&num=' + this.options.limit;
}
if (this.options.key !== null) {
apiUrl += '&key=' + this.options.key;
}
$.getJSON(apiUrl, callback);
};
RSS.prototype.render = function () {
var self = this;
this.load(function (data) {
try {
self.feed = data.responseData.feed;
self.entries = data.responseData.feed.entries;
} catch (e) {
self.entries = [];
self.feed = null;
return self.options.error.call(self);
}
var html = self.generateHTMLForEntries();
self.target.append(html.layout);
if (html.entries.length !== 0) {
if ($.isFunction(self.options.onData)) {
self.options.onData.call(self);
}
self.appendEntriesAndApplyEffects($('entries', html.layout), html.entries);
}
if (self.effectQueue.length > 0) {
self.executeEffectQueue(self.callback);
} else if ($.isFunction(self.callback)) {
self.callback.call(self);
}
});
};
RSS.prototype.appendEntriesAndApplyEffects = function (target, entries) {
var self = this;
$.each(entries, function (idx, entry) {
var $html = self.wrapContent(entry);
if (self.options.effect === 'show') {
target.before($html);
} else {
$html.css({ display: 'none' });
target.before($html);
self.applyEffect($html, self.options.effect);
}
});
target.remove();
};
RSS.prototype.generateHTMLForEntries = function () {
var self = this;
var result = { entries: [], layout: null };
$(this.entries).each(function () {
var entry = this;
var offsetStart = self.options.offsetStart;
var offsetEnd = self.options.offsetEnd;
var evaluatedString;
// offset required
if (offsetStart && offsetEnd) {
if (index >= offsetStart && index <= offsetEnd) {
if (self.isRelevant(entry, result.entries)) {
evaluatedString = self.evaluateStringForEntry(
self.options.entryTemplate, entry
);
result.entries.push(evaluatedString);
}
}
} else {
// no offset
if (self.isRelevant(entry, result.entries)) {
evaluatedString = self.evaluateStringForEntry(
self.options.entryTemplate, entry
);
result.entries.push(evaluatedString);
}
}
});
if (!!this.options.entryTemplate) {
// we have an entryTemplate
result.layout = this.wrapContent(
this.options.layoutTemplate.replace('{entries}', '<entries></entries>')
);
} else {
// no entryTemplate available
result.layout = this.wrapContent('<div><entries></entries></div>');
}
return result;
};
RSS.prototype.wrapContent = function (content) {
if ($.trim(content).indexOf('<') !== 0) {
// the content has no html => create a surrounding div
return $('<div>' + content + '</div>');
} else {
// the content has html => don't touch it
return $(content);
}
};
RSS.prototype.applyEffect = function ($element, effect, callback) {
var self = this;
switch (effect) {
case 'slide':
$element.slideDown('slow', callback);
break;
case 'slideFast':
$element.slideDown(callback);
break;
case 'slideSynced':
self.effectQueue.push({ element: $element, effect: 'slide' });
break;
case 'slideFastSynced':
self.effectQueue.push({ element: $element, effect: 'slideFast' });
break;
}
};
RSS.prototype.executeEffectQueue = function (callback) {
var self = this;
this.effectQueue.reverse();
var executeEffectQueueItem = function () {
var item = self.effectQueue.pop();
if (item) {
self.applyEffect(item.element, item.effect, executeEffectQueueItem);
} else if (callback) {
callback();
}
};
executeEffectQueueItem();
};
RSS.prototype.evaluateStringForEntry = function (string, entry) {
var result = string;
var self = this;
$(string.match(/(\{.*?\})/g)).each(function () {
var token = this.toString();
result = result.replace(token, self.getValueForToken(token, entry));
});
return result;
};
RSS.prototype.isRelevant = function (entry, entries) {
var tokenMap = this.getTokenMap(entry);
if (this.options.filter) {
if (this.options.filterLimit && (this.options.filterLimit === entries.length)) {
return false;
} else {
return this.options.filter(entry, tokenMap);
}
} else {
return true;
}
};
RSS.prototype.getFormattedDate = function (dateString) {
// If a custom formatting function is provided, use that.
if (this.options.dateFormatFunction) {
return this.options.dateFormatFunction(dateString);
} else if (typeof moment !== 'undefined') {
// If moment.js is available and dateFormatFunction is not overriding it,
// use it to format the date.
var date = moment(new Date(dateString));
if (date.locale) {
date = date.locale(this.options.dateLocale);
} else {
date = date.lang(this.options.dateLocale);
}
return date.format(this.options.dateFormat);
} else {
// If all else fails, just use the date as-is.
return dateString;
}
};
RSS.prototype.getTokenMap = function (entry) {
if (!this.feedTokens) {
var feed = JSON.parse(JSON.stringify(this.feed));
delete feed.entries;
this.feedTokens = feed;
}
return $.extend({
feed: this.feedTokens,
url: entry.link,
author: entry.author,
date: this.getFormattedDate(entry.publishedDate),
title: entry.title,
body: entry.content,
shortBody: entry.contentSnippet,
bodyPlain: (function (entry) {
var result = entry.content
.replace(/<script[\\r\\\s\S]*<\/script>/mgi, '')
.replace(/<\/?[^>]+>/gi, '');
for (var i = 0; i < RSS.htmlTags.length; i++) {
result = result.replace(new RegExp('<' + RSS.htmlTags[i], 'gi'), '');
}
return result;
})(entry),
shortBodyPlain: entry.contentSnippet.replace(/<\/?[^>]+>/gi, ''),
index: $.inArray(entry, this.entries),
totalEntries: this.entries.length,
teaserImage: (function (entry) {
try {
return entry.content.match(/(<img.*?>)/gi)[0];
}
catch (e) {
return '';
}
})(entry),
teaserImageUrl: (function (entry) {
try {
return entry.content.match(/(<img.*?>)/gi)[0].match(/src="(.*?)"/)[1];
}
catch (e) {
return '';
}
})(entry)
}, this.options.tokens);
};
RSS.prototype.getValueForToken = function (_token, entry) {
var tokenMap = this.getTokenMap(entry);
var token = _token.replace(/[\{\}]/g, '');
var result = tokenMap[token];
if (typeof result !== 'undefined') {
return ((typeof result === 'function') ? result(entry, tokenMap) : result);
} else {
throw new Error('Unknown token: ' + _token + ', url:' + this.url);
}
};
$.fn.rss = function (url, options, callback) {
new RSS(this, url, options, callback).render();
return this; // Implement chaining
};
})(jQuery);
I think a better way would be to wrap that script in an angular module and load that module as a dependancy in your main app and use it anywhere you see fit.
I have a service to share an object in my app... I want to post that object to the mongo db but when I call the function that should return the object it gives me the function's text.
The service is here:
angular.module('comhubApp')
.service('markerService', function () {
this.markers = [];
this.newMarker = { title: '',
description: '',
lat: '',
lon: '',
user: '',
created_at: '' };
// This is supposed to return the marker object
this.newMarker = function () {
return this.newMarker;
};
this.setTitle = function (title) {
this.newMarker.title = title;
console.log('title service set: ' + title);
};
this.setDescription = function (description) {
this.newMarker.description = description;
console.log('Description service set: ' + description);
};
this.setLat = function (lat) {
this.newMarker.lat = lat;
console.log('lat service set: ' + lat);
};
this.setLon = function (lon) {
this.newMarker.lon = lon;
console.log('lon service set: ' + lon);
};
this.reset = function () {
this.newMarker = { title: '',
description: '',
lat: '',
lon: '',
user: '',
created_at: ''};
}
this.setMarkers = function (markers) {
this.markers = markers;
}
this.markers = function () {
return this.markers;
}
this.addMarker = function (marker) {
//todo append marker
}
});
newMarker returns:
this.newMarker = function () {
return this.newMarker;
};
The Controller using the service is here
$scope.addMarker = function() {
if($scope.newMarker.title === '') {
console.log('newMarker title is empty');
return;
}
markerService.setTitle($scope.newMarker.title);
markerService.setDescription($scope.newMarker.description);
console.log(markerService.newMarker());
// $http.post('/api/markers', { name: $scope.newMarker });
// $scope.newMarker = '';
};
$scope new marker is form data.. i tried to put that right into my service with no success. Instead I out the form data into the controller then push it to the service. If there is a better way to do that please let me know.
If this service is bad in any other way let me know I am new to all this and so I followed another answer I saw on here.
You are overriding your object with function. Just give them different names and it should work just fine.
this.newMarker = { ... };
this.getNewMarker = function () { return this.newMarker };
EDIT:
You should also always create new instance from marker. Otherwise you just edit the same object all the time. Here is example I made. Its not best practice but hope you get the point.
angular.module('serviceApp', [])
.factory('Marker', function () {
function Marker() {
this.title = '';
this.descrpition = '';
}
// use setters and getters if you want to make your variable private
// in this example we are not using these functions
Marker.prototype.setTitle = function (title) {
this.title = title;
};
Marker.prototype.setDescription = function (description) {
this.description = description;
};
return Marker;
})
.service('markerService', function (Marker) {
this.markers = [];
this.getNewMarker = function () {
return new Marker();
}
this.addMarker = function (marker) {
this.markers.push(marker);
}
})
.controller('ServiceCtrl', function ($scope, markerService) {
$scope.marker = markerService.getNewMarker();
$scope.addMarker = function () {
markerService.addMarker($scope.marker);
$scope.marker = markerService.getNewMarker();
}
$scope.markers = markerService.markers;
});
You could also create Marker in controller and use markerService just to store your object.
And working demo:
http://jsfiddle.net/3cvc9rrs/
So, that function is the problem. I was blindly following another example and it was wrong in my case. The solution is to remove that function and access markerService.newMarker directly.
I am still a big enough noob that I am not sure why the call was returning the function as a string. It seems to have something to do with how it is named but it is just a guess.
I'm trying to write a jasmine test to check if data is entered into an array. I have the view model, the unit (jasmine) test file, and the stage file to set up the information for the unit tests. The error that I am getting is saying that my array is undefined. The error message says "Expected undefined to be {SiteId: 0, FirstName: 'Jon', LastName: 'Walker', ObjectState: 1}"
This is my viewmodel. I am trying to test self.addPatient:
var EF = (function () {
return {
ObjectState: {
Unchanged: 0,
Added: 1,
Modified: 2,
Deleted: 3
}
};
})();
var patientMapping = {
'Patients': {
key: function (patient) {
return ko.utils.unwrapObservable(patient.PatientId);
},
create: function (options) {
return new PatientViewModel(options.data);
}
}
};
PatientViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, {}, self);
self.flagPatientAsEdited = function () {
if (self.ObjectState() != ObjectState.Added) {
self.ObjectState(ObjectState.Modified);
}
return true;
},
self.FullName = ko.computed(function () {
return (self.FirstName() + " " + self.LastName());
});
}
SiteViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, patientMapping, self);
self.save = function () {
$.ajax({
url: "/Site/Save/",
type: "POST",
data: ko.toJSON(self),
contentType: "application/json",
success: function (data) {
if (data.siteViewModel != null) {
alert("Changes were saved successfully.");
ko.mapping.fromJS(data.siteViewModel, {}, self);
}
if (data.newLocation != null) {
window.location = data.newLocation;
}
}
});
},
self.flagSiteAsEdited = function () {
if (self.ObjectState() != ObjectState.Added) {
self.ObjectState(ObjectState.Modified);
}
return true;
},
self.addPatient = function () {
var patient = new PatientViewModel({ SiteId: 0, FirstName: "", LastName: "", ObjectState: ObjectState.Added });
self.Patients.push(patient);
},
self.deletePatient = function (patient) {
self.Patients.remove(this);
if (patient.PatientId() > 0 && self.PatientsToDelete.indexOf(patient.PatientId()) == -1) {
self.PatientsToDelete.push(patient.PatientId());
}
};
}
This is my stage file to initialize information for the jasmine tests:
//staged data for view model testing
var OBP = OBP || {};
OBP.Testing = (function () {
var reset = function () {
var isReadOnly = true;
window.isReadOnly = true;
window.ObjectState = 2;
window.FirstName = "Joe";
window.LastName = "Mitchell";
window.SiteId = 5;
window.patient = { "SiteId": 0, "FirstName": "Jon", "LastName": "Walker", "ObjectState": 1 };
window.patientDelete = { "PatientId": 1, "SiteId": 0, "FirstName": "Frank", "LastName": "Smith", "ObjectState": 1 };
};
return {
reset: reset
};
})();
OBP.Testing.reset();
This is my jasmine test itself that is failing:
/// <reference path="../../lib/jasmine.js"/>
/// <reference path="../../lib/mock-ajax.js" />
/// <reference path="../../jquery-2.1.3.min.js" />
/// <reference path="../../lib/jasmin-jquery.js" />
/// <reference path="../../bootstrap.js" />
/// <reference path="../../knockout-3.2.0.js" />
/// <reference path="../../knockout.mapping-latest.js" />
/// <reference path="SiteViewModelStage.js" />
/// <reference path="../../siteviewmodel.js" />
describe("Add patient check:", function () {
beforeEach(function () {
OBP.Testing.reset()
});
var Patients = new Array();
var viewModel = new SiteViewModel({ patient: window.patient });
it("Add patient works", function () {
expect(Patients[0]).toBe(patient);
});
});
Any help debugging my jasmine test would be greatly appreciated.
I made Patients and observable array in the viewModel:
self.Patients = ko.observableArray();
Edited addPatient in the viewModel:
self.addPatient = function (patient) {
self.Patients.push(patient);
},
and modified the "Add patient check" unit test:
describe("Add patient check:", function () {
beforeEach(function () {
OBP.Testing.reset()
});
var viewModel = new SiteViewModel({ patient: window.patient });
viewModel.addPatient(new PatientViewModel({ PatientId: 1, SiteId: 0, FirstName: "", LastName: "", ObjectState: ObjectState.Added }));
it("Add patient works", function () {
expect(viewModel.Patients().length == 1).toBe(true);
});
});
These changes to the viewmodel and unit test solved my problem. From here I am also able to check the name of the patient and do other tests to verify. The explanation from Xv in the comments helped lead me to this solution