Requirejs load different js file inside define() - javascript

My webpage has different js components that handles different business logic. And I'm using RequireJS to manage dependencies. I want to dynamically load the component which is needed.
For example:
define(['something'],function(something){
var processor;
if(userRole === "student"){
//I want to dynamically load studentProcessor.js here;
}
else if(userRole === "teacher"){
//I want to dynamically load teacherProcessor.js here;
}
processor.process();
});
Really appreciate if anyone can tell me what is the best way to handle this.

I think the cleanest way to handle this is having a sort of mapping from user roles to processors.
Then in your function, you still need to check that userRole is set, and a processor for the role is defined.
// This is where processors are associated to user roles.
var roleToProcessorMapping =
{
student: './studentProcessor.js',
teacher: './teacherProcessor.js'
};
define(['something'],function(something){
if(userRole){
var processorPath = roleToProcessorMapping[userRole];
if(processorPath){
require([processorPath], function(processor){
processor.process();
});
return;
}
}
// handle error
});

Related

Using Ezoic (or Adsense) with VueJS v2

I have a new version of my site which uses VueJS v2 (the previous one didn't). The main code is placed inside <div id="app"></div> and Vue is initiated. The issue is that I partner with an advertising company called Ezoic that injects ads through using AI onto the page, but these ads aren't displaying properly. I believe it is related to these errors:
https://pagead2.googlesyndication.com/pagead/show_ads.js
show_ads.js:53 Failed to execute 'write' on 'Document': It isn't
possible to write into a document from an asynchronously-loaded
external script unless it is explicitly opened.
Ezoic works with Google Ad Exchange, so I believe it is the above line that's related to the issue.
I'm wondering, is there any way in which I could make my application compatible with Ezoic/Adsense? I thought about having Vue on the page only where needed, rather than the entire page (<div id="app"></div> goes from the start of body to the end of body), but this would mean I need multiple Vue instances running as I have components at the top (search box) and also throughout the pages.
I have no access to the code that Ezoic inject onto the page as this is done on their end (my site uses their DNS and they modify the response before sending to the visitor, to include the ad code). Ezoic team is also having a look into this issue presently but any information I could pass along could be helpful!
At the request of Dynamic Remo I am submitting an answer for Ezoic's standalone implementation that is compatible with Vue.
I will however preface this with they absolutely hate it when you install it this way and essentially refuse to support it. - with that said you have way more control over placement
The Solution:
First add the following script tag somewhere outside or your custom defined root element.
<script type="text/javascript" src="//www.ezojs.com/ezoic/sa.min.js" async=""></script>
In your vue component you will need to create all your placeholder elements with a id of "ezoic-pub-ad-placeholder-xx" where xx is replaced by the actual id found in your enzoic dashboard. Dynamic creation does work as long as there is a matching id in ezoic so you have two options:
Dynamic:
<div v-for="placeholderId in ezoicArray" :id="'ezoic-pub-ad-placeholder-' + placeholderId" class="ezoic"></div>
Standard:
<div id="ezoic-pub-ad-placeholder-102" class="ezoic"></div>
To actually display ads in your placeholders you can use this function I wrote. Just call it when the component is mounted.
ezoic(placeholderList) {
if (window.ezstandalone !== undefined) {
window.ezoicPlaceholderArray = window.ezoicPlaceholderArray || [];
// Add Placeholders to Array
placeholderList.forEach((placeholder) => {
this.addPlaceholderOnce(window.ezoicPlaceholderArray, placeholder);
});
// Enable Once - Refresh on Change
window.ezoicRefreshed = false;
window.ezoicEnabled = window.ezoicEnabled || false;
// Next Tick Ensures All Enzoic Blocks Are Loaded
this.$nextTick(() => {
// On First Load We Must Enable
if (!window.ezoicEnabled) {
window.ezstandalone.define(window.ezoicPlaceholderArray);
window.ezoicPlaceholderArray = null;
console.log('ezoic defined and array reset');
window.ezstandalone.enable();
console.log('ezoic enabled');
window.ezstandalone.display();
console.log('ezoic displayed');
window.ezoicEnabled = true;
window.ezoicRefreshed = true;
}
// On Refresh We Have To Destroy & Refresh
if (!window.ezoicRefreshed) {
window.ezstandalone.destroy();
console.log('ezoic destroyed');
window.ezstandalone.define(window.ezoicPlaceholderArray);
window.ezoicPlaceholderArray = null;
console.log('ezoic refresh defined and array reset');
window.ezstandalone.refresh();
console.log('ezoic refreshed');
window.ezoicRefreshed = true;
}
});
} else {
console.log('Error: Missing Ezoic Standalone');
}
},
addPlaceholderOnce(array, placeholder) {
if (!array.includes(placeholder)) {
array.push(parseInt(placeholder));
console.log('ezoic-pub-ad-placeholder-' + placeholder + ' - Created');
}
},
If you need to change your placeholder setup for different reasons as I do simply add window.ezoicRefreshed = false; to the appropriate lifecycle hook. In my case I have it in beforeUnmount because each route gets a custom placeholder list.
Hopefully this helps!

Dynamically add to a list in angular

Goal: a dynamically generated list from external source.
I've set up a simple angular app that gets a list of events from an external JSON source. I want the list to update when events are added from the external source. It's currently working, but I have one problem and three questions:
1) I'm currently rewriting the list every 15 seconds. How do I just add to the end of the list without rewriting the list? (problem and question)
2) Is there another, better way to keep up to date with the external list? I'm trying to follow "RESTful" techniques, does that mean I should rely on the client side code to poll every so many seconds the way I'm doing? (best practice question)
3) Is setting the timeout in the controller best practice? Because it's controlling the action on the page?(best practice/comprehension question)
var eventModule = angular.module('eventModule', []);
eventModule.controller('eventControlller',
function($scope, $timeout, eventList) {
$scope.events = eventList.getAllEvents().success(
function(events) {$scope.events = events});
var poll = function() {
$timeout(function() {
$scope.events = eventList.getAllEvents().success(
function(events) {$scope.events = events});
poll();
}, 15000);
};
poll();
});
eventModule.factory('eventList', function($http) {
var url = "http://localhost/d8/events/request";
return {
getAllEvents: function() {
return $http.get(url);
}
};
});
If the list is an array, and you want to add new members to it, there are a few different ways. One way is to use the prototype.concat() function, like so:
function(events) {
$scope.events = $scope.events.concat(events)
});
If you cannot use that then you can go for loops solution:
function concatenateEvents(events) {
events.forEach(function(element) {
events.push(element);
}
}
Regarding the best ways to update the list, it depends on your requirements. If 15 seconds is not too long for you, then you can keep this logic, but if you need to speed up the response time, or even make it real time, then you need to emulate server-push architecture, which is different than the default web architecture, which is request-response architecture. Basically you may want to explore web sockets, and/or long polling, or reverse ajax, or comet... has many names. Web sockets is the recommended solution, others are only in case you have to use some non-compatible browsers.
Regarding the third question, I honestly don't know. Truly it doesn't feel good to control the UI from within your controller, but as I don't really know what your app is supposed to be doing, I don't know whether this is actually a bad way to do it.
Hope this helps!
EDIT - forgot to add another important point: You don't need to assign the eventList.getAllEvents() to $scope.events, as you are doing that in the callback handler function.
Perhaps you can modify your controller to something like this:
eventModule.controller('eventControlller', function($scope, $timeout, eventList) {
eventList.getAllEvents().success(
function(events) {
$scope.events = events
});
var poll = function() {
$timeout(function() {
eventList.getAllEvents().success(
function(events) {$scope.events = events});
poll();
}, 15000);
};
poll();
});

Two-way data binding for a Meteor app

I've built an app that is form-based. I want to enable users to partially fill out a form, and then come back to it at a later date if they can't finish it at the present. I've used iron router to create a unique URL for each form instance, so they can come back to the link. My problem is that Meteor doesn't automatically save the values in the inputs, and the form comes up blank when it is revisited/refreshes. I tried the below solution to store the data in a temporary document in a separate Mongo collection called "NewScreen", and then reference that document every time the template is (re)rendered to auto fill the form. However, I keep getting an error that the element I'm trying to reference is "undefined". The weird thing is that sometimes it works, sometimes it doesn't. I've tried setting a recursive setTimeout function, but on the times it fails, that doesn't work either. Any insight would be greatly appreciated. Or, if I'm going about this all wrong, feel free to suggest a different approach:
Screens = new Meteor.Collection('screens') //where data will ultimately be stored
Forms = new Meteor.Collection('forms') //Meteor pulls form questions from here
NewScreen = new Meteor.Collection('newscreen') //temporary storage collection
Roles = new Meteor.Collection('roles'); //displays list of metadata about screens in a dashboard
//dynamic routing for unique instance of blank form
Router.route('/forms/:_id', {
name: 'BlankForm',
data: function(){
return NewScreen.findOne({_id: this.params._id});
}
});
//onRendered function to pull data from NewScreen collection (this is where I get the error)
Template.BlankForm.onRendered(function(){
var new_screen = NewScreen.findOne({_id: window.location.href.split('/')[window.location.href.split('/').length-1]})
function do_work(){
if(typeof new_screen === 'undefined'){
console.log('waiting...');
Meteor.setTimeout(do_work, 100);
}else{
$('input')[0].value = new_screen.first;
for(i=0;i<new_screen.answers.length;i++){
$('textarea')[i].value = new_screen.answers[i];
}
}
}
do_work();
});
//onChange event that updates the NewScreen document when user updates value of input in the form
'change [id="on-change"]': function(e, tmpl){
var screen_data = [];
var name = $('input')[0].value;
for(i=0; i<$('textarea').length;i++){
screen_data.push($('textarea')[i].value);
}
Session.set("updateNewScreen", this._id);
NewScreen.update(
Session.get("updateNewScreen"),
{$set:
{
answers: screen_data,
first: name
}
});
console.log(screen_data);
}
If you get undefined that could mean findOne() did not find the newscreen with the Id that was passed in from the url. To investigate this, add an extra line like console.log(window.location.href.split('/')[window.location.href.split('/').length-1], JSON.stringify(new_screen));
This will give you both the Id from the url and the new_screen that was found.
I would recommend using Router.current().location.get().path instead of window.location.href since you use IR.
And if you're looking for two way binding in the client, have a look at Viewmodel for Meteor.

Using Piwik for a Single Page Application

Building a single page / fat client application and I'm wondering what the best practice is for including and tracking using http://piwik.org/
I'd like to use Piwik in a way that is architecturally sound and replacable with a different library in the future.
It seems that there are two basic options for tracking with Piwik:
Fill up a global _paq array with commands, then load the script (it's unclear to me how to record future "page" views or change variables though)
Get and use var myTracker = Piwik.getTracker()
_paq approach:
myApp.loadAnalytics = function() { /* dynamically insert piwik.php script */ }
myApp.track = function(pageName) {
window._paq = window._paq || [];
_paq.push(['setDocumentTitle', pageName]);
_paq.push(["trackPageView"]);
}
myApp.loadAnalytics()
// Then, anywhere in the application, and as many times as I want (I hope :)
myApp.track('reports/eastWing') // Track a "page" change, lightbox event, or anything else
.getTracker() approach:
myApp.loadAnalytics = function() { /* dynamically insert piwik.php script */ }
myApp.track = function(pageName) {
myApp.tracker = myApp.tracker || Piwik.getTracker('https://mysite.com', 1);
myApp.tracker.trackPageView(pageName);
}
myApp.loadAnalytics()
// Then, anywhere in the application, and as many times as I want (I hope :)
myApp.track('reports/eastWing') // Track a "page" change, lightbox event, or anything else
Are these approaches functionally identical? Is one preferred over another for a single page app?
To have the tracking library used (eg. piwik) completely independent from your application, you would need to write a small class that will proxy the functions to the Piwik tracker. Later if you change from Piwik to XYZ you can simply update this proxy class rather than updating multiple files that do some tracking.
The Async code is a must for your app (for example a call to any 'track*' method will send the request)
The full solution using .getTracker looks like this:
https://gist.github.com/SimplGy/5349360
Still not sure if it would be better to use the _paq array instead.

initializeLayout and asynchronous loading

Currently I'm loading data asynchronously via data.js as provided by the Grid app template. The problem exists where groupedItems.js (the "Hub" page) calls _initializeLayout in the ready handler before the Data in the global WinJS namespace is set due to the asynchronous nature of the StorageFile class.
In data.js:
fileNames.forEach(function (val, index, arr) {
var uri = new Windows.Foundation.Uri('ms-appx:///data/' + val + '.geojson');
Windows.Storage.StorageFile.getFileFromApplicationUriAsync(uri).then(function (file) {
Windows.Storage.FileIO.readTextAsync(file).then(function (contents) {
// ... read, parse, and organize the data ...
// Put the data into the global namespace
WinJS.Namespace.define("Data", {
items: groupedItems,
groups: groupedItems.groups,
getItemReference: getItemReference,
getItemsFromGroup: getItemsFromGroup,
resolveGroupReference: resolveGroupReference,
resolveItemReference: resolveItemReference
});
});
});
}
In groupedItems.js:
// ...
// This function updates the ListView with new layouts
_initializeLayout: function (listView, viewState) {
/// <param name="listView" value="WinJS.UI.ListView.prototype" />
if (viewState === appViewState.snapped) {
listView.itemDataSource = Data.groups.dataSource;
listView.groupDataSource = null;
listView.layout = new ui.ListLayout();
} else {
listView.itemDataSource = Data.items.dataSource;
listView.groupDataSource = Data.groups.dataSource;
listView.layout = new ui.GridLayout({ groupHeaderPosition: "top" });
}
},
// ....
Seeing as I cannot move this code out of this file into the done() function of the Promise in data.js, how do I make the application wait until Data is initialized in the WinJS namespace prior to initializing the layout?
You have two asynchronous operations in progress (loading of the data and loading of the page) and one action (initializing the grid) that needs to happen only after both asynchronous operations are complete (page is loaded, data is available). There are a lot of approaches to solve this depending upon what architectural approach you want to take.
The brute force method is that you create a new function that checks to see if both the document is ready and the data is loaded and, if so, it calls _initializeLayout(). You then call that function in both places (where the doc is loaded and when the data is available) and it will execute only when both conditions are satisfied. It appears that you can tell if the data is loaded by checking for the existence of the global Data item and the its relevant properties.
There are more involved solutions that are architecturally a little cleaner. For example, in your doc ready handler, you can check to see if the data is available yet. If it is, you just initialize the layout. If, not you install a notification so that when the data is available, your callback will get called and you can then initialize the layout. If the data loading code doesn't currently have a notification scheme, then you create one that can be used by any client who wants to be called when the data has been loaded. This has the advantage over the first method in that the data loading code doesn't have to know anything about the grid. The grid does have to know about the data - which makes sense because the grid requires the data.
There are surely ways to use the promise/done system to do this too though I'm not personally familiar enough with it to suggest a good way to do it using that.

Categories