How to structure multiple pages with RequireJS - module

How to structure multiple pages with RequireJS? Is, like the following sample, declaring every class in app.js is the right thing to do? Has every html file to declare the <script data-main="src/main" src="src/require.js"></script>?
What I want to avoid is loading all the script when a user reach the first page of a site.
main.js defining all external dependencies:
require(
{
baseUrl:'/src'
},
[
"require",
"order!http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js",
"order!http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js",
"order!http://ajax.cdnjs.com/ajax/libs/underscore.js/1.1.6/underscore-min.js",
"order!http://ajax.cdnjs.com/ajax/libs/backbone.js/0.3.3/backbone-min.js"
],
function (require) {
require(["app"], function (app) {
app.start();
});
}
);
app.js file defining every component:
define([ "product/ProductSearchView",
"product/ProductCollection"
], function (ProductSearchView,
ProductCollection) {
return {
start: function() {
var products = new ProductCollection();
var searchView = new ProductSearchView({ collection: products });
products.fetch();
return {};
}
}
});

You can require files within your existing module. So say when someone clicks a link you could trigger a function that does the following:
// If you have a require in your other module
// The other module will execute its own logic
require(["module/one"], function(One) {
$("a").click(function() {
require(["new/module"]);
});
});
// If you have a define in your other module
// You will need to add the variable to the require
// so you can access its methods and properties
require(["module/one"], function(One) {
$("a").click(function() {
require(["new/module"], function(NewModule) {
NewModule.doSomething();
});
});
});

This is a complete example of how this all works; require.js and order.js are in the same directory as the app's JS files.
<html>
<head>
<script data-main="js/test" src="js/require.js"></script>
</head>
<body>
<button>Clickme</button>
</body>
</html>
test.js (in js folder)
require(
{
baseUrl:'/js'
},
[
"order!//ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js",
"order!//ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js"
],
function () {
require(["app"], function (app) {
app.start();
});
}
);
app.js (in js folder) with a on-demand load of Employee.js:
define([], function () {
return {
start: function() {
$('button').button();
$('button').click(function() {
require(['Employee'], function(Employee) {
var john = new Employee('John', 'Smith');
console.log(john);
john.wep();
});
});
return {};
}
}
});
Employee.js (in js folder):
define('Employee', function () {
return function Employee(first, last) {
this.first = first;
this.last = last;
this.wep = function() {
console.log('wee');
}
};
});

Related

VueJS - function in import js file not getting triggered

We are building a web application using Vue JS and PHP, we are new to Vue JS. The server-side execution is fine, the API is able to fetch data as JSON. While trying out a static array display before making the API call, we find that the function in imported "app.js" is not getting called and the table displayed is empty. Please let us know what we might be doing wrong. Appreciate your help.
import Vue from 'vue';
export const MY_CONST = 'Vue.js';
export let memberList = new Vue({
el: '#members',
data: {
members: []
},
mounted: function () {
this.getAllMembers();
},
methods: {
getAllMembers: function () {
/*
axios.get("https://xxxxxx.com/services/api.php")
.then(function (response) {
memberList.members = response.data.members;
});
*/
memberList.members = [{ "empname": "Dinesh Dassss" },
{ "empname": "Kapil Koranne" }];
}
}
});
This is the Vue component. The members object is empty.
<script>
import * as mykey from './app.js'
export default {
name: 'Home',
props: {
msg: String
},
data() {
return {
message: `Hello ${mykey.MY_CONST}!`,
members: mykey.memberList.members
}
}
};
</script>
You can also use this reference for current instance reference:
getAllMembers: function () {
var me = this;
/*
axios.get("https://xxxxxx.com/services/api.php")
.then(function (response) {
// direct this not works here but we have
//saved this in another variable and scope of a var is there
me.members = response.data.members;
});
*/
// this reference works fine here.
this.members = [{ "empname": "Dinesh Dassss" },
{ "empname": "Kapil Koranne" }];
}

Nightwatch: Separate xpath into another files

How to separate xpath into separate file and use it in nightwatch automation testing?
EDIT:
I running a page object pattern example and found some errors.
Errors: TypeError: browser.page.url is not a function
Please help on this.
module.exports = {
url: 'http://localhost:63916/Login/Login',
elements: {
username: {
selector: '//*[#id="inputName"]',
locateStrategy: 'xpath'
}
}
};
module.exports = (function(settings) {
settings.test_workers = false;
return settings;
})(require('./nightwatch.json'));
//const data = require('./data.js')
module.exports = {
'Login' : function (browser) {
var page = browser.page.url();
page.navigate()
.setValue('#username', 'peter')
browser.end()
}
};
So assuming the page object is defined in pages directory. You need to change your nightwatch.conf.js like below
nightwatch.conf.js
module.exports = (function(settings) {
settings.test_workers = false;
settings.page_objects_path = "./pages";
return settings;
})(require('./nightwatch.json'));
Your pages has a file named main.js
pages/main.js
module.exports = {
url: 'https://gmail.com',
elements: {
username: {
selector: '//*[#id="identifierId"]',
locateStrategy: 'xpath'
},
next: {
selector: '#identifierNext span',
locateStrategy: 'css'
}
}
};
And then you test is like below
tests/test.main.js
module.exports = {
'Login' : function (browser) {
var page = browser.page.main();
page.navigate()
.setValue('#username', 'peterlalwani')
.click('#next')
.waitForElementNotPresent("#username", 10000);
browser.saveScreenshot("./so.png")
browser.end()
}
};
Now when you run it, it creates a so.png
I created a sample repo for you to clone and see the above
https://github.com/tarunlalwani/nightwatch-page-object-so.git
PS: It is important to note that var page = browser.page.main(); means it would load main.js from the pages folder here.

Open a VueJS component on a new window

I have a basic VueJS application with only one page.
It's not a SPA, and I do not use vue-router.
I would like to implement a button that when clicked executes the window.open() function with content from one of my Vue Components.
Looking at the documentation from window.open() I saw the following statement for URL:
URL accepts a path or URL to an HTML page, image file, or any other resource which is supported by the browser.
Is it possible to pass a component as an argument for window.open()?
I was able to use some insights from an article about Portals in React to create a Vue component which is able to mount its children in a new window, while preserving reactivity! It's as simple as:
<window-portal>
I appear in a new window!
</window-portal>
Try it in this codesandbox!
The code for this component is as follows:
<template>
<div v-if="open">
<slot />
</div>
</template>
<script>
export default {
name: 'window-portal',
props: {
open: {
type: Boolean,
default: false,
}
},
data() {
return {
windowRef: null,
}
},
watch: {
open(newOpen) {
if(newOpen) {
this.openPortal();
} else {
this.closePortal();
}
}
},
methods: {
openPortal() {
this.windowRef = window.open("", "", "width=600,height=400,left=200,top=200");
this.windowRef.addEventListener('beforeunload', this.closePortal);
// magic!
this.windowRef.document.body.appendChild(this.$el);
},
closePortal() {
if(this.windowRef) {
this.windowRef.close();
this.windowRef = null;
this.$emit('close');
}
}
},
mounted() {
if(this.open) {
this.openPortal();
}
},
beforeDestroy() {
if (this.windowRef) {
this.closePortal();
}
}
}
</script>
The key is the line this.windowRef.document.body.appendChild(this.$el); this line effectively removes the DOM element associated with the Vue component (the top-level <div>) from the parent window and inserts it into the body of the child window. Since this element is the same reference as the one Vue would normally update, just in a different place, everything Just Works - Vue continues to update the element in response to databinding changes, despite it being mounted in a new window. I was actually quite surprised at how simple this was!
You cannot pass a Vue component, because window.open doesn't know about Vue. What you can do, however, is to create a route which displays your component and pass this route's URL to window.open, giving you a new window with your component. Communication between the components in different windows might get tricky though.
For example, if your main vue is declared like so
var app = new Vue({...});
If you only need to render a few pieces of data in the new window, you could just reference the data model from the parent window.
var app1 = window.opener.app;
var title = app.title;
var h1 = document.createElement("H1");
h1.innerHTML = title;
document.body.appendChild(h1);
I ported the Alex contribution to Composition API and works pretty well.
The only annoyance is that the created window ignores size and position, maybe because it is launched from a Chrome application that is fullscreen. Any idea?
<script setup lang="ts">
import {ref, onMounted, onBeforeUnmount, watch, nextTick} from "vue";
const props = defineProps<{modelValue: boolean;}>();
const emit = defineEmits(["update:modelValue"]);
let windowRef: Window | null = null;
const portal = ref(null);
const copyStyles = (sourceDoc: Document, targetDoc: Document): void => {
// eslint-disable-next-line unicorn/prefer-spread
for(const styleSheet of Array.from(sourceDoc.styleSheets)) {
if(styleSheet.cssRules) {
// for <style> elements
const nwStyleElement = sourceDoc.createElement("style");
// eslint-disable-next-line unicorn/prefer-spread
for(const cssRule of Array.from(styleSheet.cssRules)) {
// write the text of each rule into the body of the style element
nwStyleElement.append(sourceDoc.createTextNode(cssRule.cssText));
}
targetDoc.head.append(nwStyleElement);
}
else if(styleSheet.href) {
// for <link> elements loading CSS from a URL
const nwLinkElement = sourceDoc.createElement("link");
nwLinkElement.rel = "stylesheet";
nwLinkElement.href = styleSheet.href;
targetDoc.head.append(nwLinkElement);
}
}
};
const openPortal = (): void => {
nextTick().then((): void => {
windowRef = window.open("", "", "width=600,height=400,left=200,top=200");
if(!windowRef || !portal.value) return;
windowRef.document.body.append(portal.value);
copyStyles(window.document, windowRef.document);
windowRef.addEventListener("beforeunload", closePortal);
})
.catch((error: Error) => console.error("Cannot instantiate portal", error.message));
};
const closePortal = (): void => {
if(windowRef) {
windowRef.close();
windowRef = null;
emit("update:modelValue", false);
}
};
watch(props, () => {
if(props.modelValue) {
openPortal();
}
else {
closePortal();
}
});
onMounted(() => {
if(props.modelValue) {
openPortal();
}
});
onBeforeUnmount(() => {
if(windowRef) {
closePortal();
}
});
</script>
<template>
<div v-if="props.modelValue" ref="portal">
<slot />
</div>
</template>

How to extend a vuejs with a method?

I'm currently trying to wrap my head around how to extend a vuejs instance. Specifically I want to separate an instance, so that I can reuse the base of an instance (the element and the data). I currently have different (laravel/blade) views for adding and editing items (domains), and I want to share a vuejs instance between these two, but I don't want to have the same code (the base) in two places.
Basically, what I'm searching for is the following:
<script type="text/javascript">
var vue = new Vue({
el: '#domain',
data: {
form: {
'name' : '',
'git_repo' : '',
'auto_deploy' : '',
'create_db' : ''
},
ajaxResponse : ''
}
});
</script>
<script type="text/javascript">
Vue.extend('domain_methods', {
methods: {
postDomain: function () {
this.$http.post('{{ route('domain.store') }}', function (data, status, request) {
this.$set('ajaxResponse', data);
}, {
data: this.form
} ).error(function (data, status, request) {
this.$set('ajaxResponse', data);
});
}
}
});
</script>
But that obviously doesn't work. I just want to use the postDomain() method within the #domain element, without the method being written in the initial creation of the instance.
Thanks in advance!
Be careful – you are conflating the usage of .extend() with that of .component(). They do very different things. This section of the docs has more information on the differences:
http://vuejs.org/guide/components.html
In this particular case, just declare your top level Vue class via .extend() and then instantiate a subclass of it by using it as a constructor. This gives you inheritance-like behavior.
So, for instance:
<script type="text/javascript">
var MainVue = Vue.extend({
data: function () {
return {
form: {
'name' : '',
'git_repo' : '',
'auto_deploy' : '',
'create_db' : ''
},
ajaxResponse : ''
};
}
});
</script>
<script type="text/javascript">
var secondary_vue = new MainVue({
el: '#domain',
methods: {
postDomain: function () {
this.$http.post('{{ route('domain.store') }}', function (data, status, request) {
this.$set('ajaxResponse', data);
}, {
data: this.form
} ).error(function (data, status, request) {
this.$set('ajaxResponse', data);
});
}
}
});
</script>

Sinon JS "Attempted to wrap ajax which is already wrapped"

I got the above error message when I ran my test. Below is my code (I'm using Backbone JS and Jasmine for testing). Does anyone know why this happens?
$(function() {
describe("Category", function() {
beforeEach(function() {
category = new Category;
sinon.spy(jQuery, "ajax");
}
it("should fetch notes", function() {
category.set({code: 123});
category.fetchNotes();
expect(category.trigger).toHaveBeenCalled();
}
})
}
You have to remove the spy after every test. Take a look at the example from the sinon docs:
{
setUp: function () {
sinon.spy(jQuery, "ajax");
},
tearDown: function () {
jQuery.ajax.restore(); // Unwraps the spy
},
"test should inspect jQuery.getJSON's usage of jQuery.ajax": function () {
jQuery.getJSON("/some/resource");
assert(jQuery.ajax.calledOnce);
assertEquals("/some/resource", jQuery.ajax.getCall(0).args[0].url);
assertEquals("json", jQuery.ajax.getCall(0).args[0].dataType);
}
}
So in your jasmine test should look like this:
$(function() {
describe("Category", function() {
beforeEach(function() {
category = new Category;
sinon.spy(jQuery, "ajax");
}
afterEach(function () {
jQuery.ajax.restore();
});
it("should fetch notes", function() {
category.set({code: 123});
category.fetchNotes();
expect(category.trigger).toHaveBeenCalled();
}
})
}
What you need in the very beginning is:
before ->
sandbox = sinon.sandbox.create()
afterEach ->
sandbox.restore()
Then call something like:
windowSpy = sandbox.spy windowService, 'scroll'
Please notice that I use coffee script.