So I'm building this Pencilblue website. Pencilblue is based on the MEAN stack.
I'm trying to get a search function going. I need to declare a module.
Pencilblue does it like this:
ClientJs.getAngularController = function(objects, modules, directiveJS) {
if(!util.isArray(modules) || modules.length > 0) {
modules = ['ngRoute'];
}
var angularController = 'var pencilblueApp = angular.module("pencilblueApp", ' + JSON.stringify(modules) + ')';
So the 2nd line is telling me the modules are loaded from somewhere else, unless there are none, in which case, the modules = ['ngRoute']; should be loaded.
What I came up with is this:
ClientJs.getAngularController = function(objects, modules, directiveJS) {
if( modules.length > 0) {
modules = ['ngRoute', 'elasticui'];
}
var angularController = "var pencilblueApp = angular.module('pencilblueApp', " + JSON.stringify(modules) + ").constant('euiHost', 'localhost:9200')";
While this works, I'm not sure it's an orthodox way of doing it and I might need to add others in the future. I'd really appreciate if someone could help out and tell me the right way to add this ['elasticui'] module in Pencilblue, together with the last part, the .constant('euiHost', 'localhost:9200')";
I'm adding ElasticUI to my project, and the only thing that I had problems with was adding this step: angular.module('yourApp', ['elasticui']).constant('euiHost', 'http://localhost:9200');
It's rather trivial to do it in a MEAN stack or plain Angular.js, but it's quite confusing in Pencilblue.
Would really appreciate a detailed response on how to do this the proper way. Thanks.
Related
I have build this small react app with 3 components showing weatherforcaset from an api.
I am not sure how I have handled the utils functions in a separate node module is the correct way as it's pretty adhoc, mainly to do with the date handling and creating another 5 arrays with the data received from the api.
https://github.com/c-science1/weatherForecastReact/
Please can someone advise me if there is a better way if doing the same?
Many Thanks!
It looks like you can minimize the code in the function while keeping the DRY principle. You should refactor the following method:
this.createNewLists = function (dayName, itemP){
let itemDate = new Date(itemP.dt_txt);
let currentDate = new Date();
let nextDate = new Date(); ;
if (itemDate.getDate() == currentDate.getDate() ){
this.day1.push(itemP);
this.day1Name = dayName;
}
nextDate.setDate(nextDate.getDate() + 1);
if (itemDate.getDate() === nextDate.getDate()){
this.day2.push(itemP);
this.day2Name = dayName;
}
nextDate.setDate(nextDate.getDate() + 1);
if (itemDate.getDate() === nextDate.getDate()){
this.day3.push(itemP);
this.day3Name = dayName;
}
nextDate.setDate(nextDate.getDate() + 1);
if (itemDate.getDate() === nextDate.getDate()){
this.day4.push(itemP);
this.day4Name = dayName;
}
nextDate.setDate(nextDate.getDate() + 1);
if (itemDate.getDate() === nextDate.getDate()){
this.day5.push(itemP);
this.day5Name = dayName;
}
}
The pattern in this function are repeating them and you can better organize this code.
You can iterate from 1 to 5 (for the weekdays) and minimize your code and keep clean code and the DRY principle.
I would say for the size of your app it is just fine. Regarding things I might do differently, I would not use utils function wrapper, since you already have module serving as namespace.
As for weatherImage function I would probably put it into component file that uses it. If more than one component uses it, I would probably put it into components/common.js. createNewLists would probably also go to this module.
I have code similar to this:
ExamPage.prototype.enterDetailsInputData = function (modifier) {
page.sendKeys(this.modalExamName, 'Test Exam ' + modifier);
page.sendKeys(this.modalExamVersionId, 'Test exam version ' + modifier);
page.sendKeys(this.modalExamProductVersionId, 'Test exam product version ' + modifier);
page.sendKeys(this.modalExamAudienceId, 'Test exam audience ' + modifier);
page.sendKeys(this.modalExamPublishedId, '2014-06-1' + modifier);
page.sendKeys(this.modalExamPriceId, '100' + modifier);
page.sendKeys(this.modalExamDurationId, '6' + modifier);
};
Here's the page.sendKeys function. Note that currently this is not doing any return of promises or anything like that. If the function is not coded well then I welcome comments:
// page.sendkeys function
sendKeys(id: string, text: string) {
element(by.id(id)).sendKeys(text);
}
I watch as it slowly fills out each field on my screen and then repeats it again and again in more tests that follow.
Is there any way that this could be optimized or do I have to wait for one field after the other to fill and have to live with tests that take a long time to run?
I assume sendKeys is promise based. Could I for example use AngularJS $q to issue all the sendKeys at the same time and then use $q to wait for them to complete?
Potential Solution I think at least a little hackery is required no matter how you optimize it - protractor doesn't give you this out of the box. However would a small helper function like this suit your needs? What else do you need to speed up sides text inputs with ng-models?
function setNgModelToString(element, value) {
return element.getAttribute('ng-model').then(function (ngModel) {
element.evaluate('$eval("' + ngModel + ' = \'' + value + '\'") && $digest()');
});
}
Solution Example:
describe('angularjs homepage', function() {
it('should have a title', function() {
browser.get('http://juliemr.github.io/protractor-demo/');
var inputString1 = '';
var inputString2 = '';
for (var i = 0; i < 1000; i++) {
inputString1 += '1';
inputString2 += '2';
}
/* Uncomment this to see it runs much much slower when you enter each key. */
//element(by.model('second')).sendKeys(inputString1);
setNgModelToString(element(by.model('second')), inputString2);
expect(element(by.model('second')).getAttribute('value')).toEqual(inputString2);
});
});
Why does the solution work?
You need to use $eval to wrap the assignment and not just assignment as evaluate does not evaluate side effects (a nested evaluation, though... heh). Assuming that's truthy in angular expressions then $digest() is called from the &&; this causes a digest to happen, which you need to update everything since you set a value from outside the digest cycle.
Thoughts about the solution:
The whole idea behind an E2E test is to "emulate" an end-user using your app. This arguably doesn't do that as well as sending the keys one-by-one, or copy-and-paste (since pasting into elements is a valid way of entering input; it's just hard to set up due to flash, etc., see below).
Other Potential Solutions:
Copy and Paste: Create an element, enter text, copy it, paste the text sending Ctrl + V to the target element. This may require doing a bunch of fancy footwork, like using Flash (exposing the system clipboard is a security risk) and having "copy it" click an invisible flash player. See executeScript to evaluate functions on the target so that you have access to variables like window if you need that.
Parallelizing your tests. Read the official doc here and search for "shard" and then "multiple". If you're mainly worried about the duration of your entire test collection and not individual tests, scaling out your browser count is probably the way to go. However there's a good chance you are TDD-ing or something, hence needing each test to run faster.
You can do following -
ExamPage.prototype.enterDetailsInputData = function (modifier) {
var arr=[
{id:this.modalExamName, text:'Test Exam ' + modifier},
{id:this.modalExamVersionId, text:'Test exam version ' + modifier },
{id:this.modalExamProductVersionId, text:'Test exam product version ' + modifier},
{id:this.modalExamAudienceId,text:'Test exam audience ' + modifier},
{id:this.modalExamPublishedId, text:'2014-06-1' + modifier},
{id:this.modalExamPriceId, text:'100' + modifier},
{this.modalExamDurationId, text:'6' + modifier}
];
var Q = require('q'),
promises = [];
for (var i = 0; i < arr.length; i++) {
promises.push(page.sendKeys(arr[i].id, arr[i].text));
}
Q.all(promises).done(function () {
//do here whatever you want
});
};
sendKeys returns promise by default. See here -https://github.com/angular/protractor/blob/master/docs/api.md#api-webdriver-webelement-prototype-sendkeys
sendKeys(id: string, text: string) {
return element(by.id(id)).sendKeys(text);
}
If you really want to speed up the process of manipulating DOM in any way (including filling up data forms) one option to consider is to use: browser.executeScript or browser.executeAsyncScript. In such a case the webdriver let the browser execute the script on its own -- the only overhead is to send the script body to the browser, so I do not think there may be anything faster.
From what I see, you identify DOM elements by ids so it should smoothly work with the approach I propose.
Here is a scaffold of it -- tested it and it works fine:
browser.get('someUrlToBeTested');
browser.waitForAngular();
browser.executeScript(function(param1, param2, param3){
// form doc: arguments may be a boolean, number, string, or webdriver.WebElement
// here all are strings: param1 = "someClass", param2 = "someId", param3 = "someModifier"
var el1 = document.getElementsByClassName(param1)[0];
var el2 = document.getElementById(param2);
el1.setAttribute('value', 'yoohoo ' + param3);
el2.setAttribute('value', 'hooyoo ' + param3);
// depending on your context it will probably
// be needed to manually digest the changes
window.angular.element(el1).scope().$apply();
},'someClass', 'someId', 'someModifier')
small remark: If you pass webdriver.WebElement as one of your argument it will be cast down to the corresponding DOM element.
I also used browser.executeScript. But I didn't need to use $apply waitForAngular
I add a script when E2E tests are running, and put a function on global scope, don't worry it's only during E2E tests, and you can namespace if you want to.
'use strict';
function fastSendKeys(testId, value) {
//test id is just an attribute we add in our elements
//you can use whatever selector you want, test-id is just easy and repeatable
var element = jQuery('[test-id=' + '"' + testId + '"' + ']');
element.val(value);
element.trigger('input');
}
Then in your protractor test something like this (in a page object):
this.enterText = function (testId, value) {
var call = 'fastSendKeys(' + '"' + testId + '"' + ',' + '"' + value + '"' + ')';
console.log('call is ', call);
browser.executeScript(call);
};
The following worked for me when testing an angular 2 application
await browser.executeScript("arguments[0].value='" + inputText + "';", await element.by.css("#cssID"))
inspired by https://stackoverflow.com/a/43404924/6018757
I'm trying to create an XTemplate, and one of my dataIndexes has a period... so my data looks something like this:
{
"one": 1,
"two.one": 21,
"two.two": 22
}
Now, when I'm creating the template, I can't do something like {two.one} because that thinks two is the beginning of an object, and then I'm accessing its key one. I've tried doing something like {'two.one'}, but this also doesn't work, and I've tracked it down to the culprit in Ext.XTemplateCompiler.parseTag. This code is what breaks it:
// compound Javascript property name (e.g., "foo.bar")
else if (isNaN(name) && name.indexOf('-') == -1 && name.indexOf('.') != -1) {
v = "values." + name;
}
// number or a '-' in it or a single word (maybe a keyword): use array notation
// (http://jsperf.com/string-property-access/4)
else {
v = "values['" + name + "']";
}
So with my two.one string, I get into that else if, but what I really want is the else that follows right after it. Unfortunately, it doesn't seem like I can override this in an easy way... does anybody have any thoughts? I'm using Ext JS 4.2.1.
Thanks to skirtle over on the Sencha Forums, I was able to solve it:
Instead of using {two.one} or {'two.one'}, it should be {[values['two.one']]}. Using values directly was the ticket.
I have a project that can be packaged for 2 targets (mobile and desktop). Tho I still want to keep my source files in the same place since only a few of them are different, but the difference is too big tho to do it only with responsive method (pages missing on mobile, or totally different on desktop, ...) and I want to keep the packaged app as small as possible.
So I created a loader.mobile.styl and loader.desktop.styl, knowing that the packager will import one or the other depending on the target/platform it's building for:
TARGET='mobile' // or 'desktop' for loader.desktop.stylus
#import '_import' // my import helper
import('_application') // my application main stylus file requiring al others
and in _import.styl:
import(file)
#import file
#import file + '.' + TARGET
So the goal is, when you call import('_application') for example, to first import _application.styl and then _application.mobile.styl (or _application.desktop.styl if the target is desktop)
It is working great except that in most of the cases only the shared _application.styl or the specific _application.mobile.styl may exist and not the other.
So I am trying without success to find a way to do an import if exists with Stylus. If just something like fileExists or such was available I could do it, or a try...catch even without the catch block, so that if it fails it doesn't matter.
After some research I ended up writing a plugin which would replace #import directive by defining a custom import function. For those who it might help, here is how I did in my own case:
In file plugins.js:
var sysPath = require('path');
var fs = require('fs');
// here is where I defined some helper to know what is the currently building target
var conf = require('../config');
var plugin = function () {
return function (style) {
var nodes = this.nodes;
style.define('import', function (param) {
var target = conf.currentTarget(),
realPath = sysPath.dirname(conf.ROOT + sysPath.sep + param.filename),
baseName = param.string.replace(/\.styl$/, ''),
targetFile = baseName + '.' + target + '.styl',
file = baseName + '.styl',
res = new nodes.Root();
// first include the file (myFile.styl for example) if it exists
if (fs.existsSync(realPath + sysPath.sep + file)) {
res.push(new nodes.Import(new nodes.String(file)));
}
// then include the target specific file (myFile.mobile.styl for example) if it exists
if (fs.existsSync(realPath + sysPath.sep + targetFile)) {
res.push(new nodes.Import(new nodes.String(targetFile)));
}
return res;
});
};
};
module.exports = plugin;
in file loader.styl:
use('plugins.js')
import('application')
So then any import('xyz') would import xyz.styl if it exists and xyz.mobile.styl (or xyz.desktop.styl if desktop is the target) if it exists.
I have created an array which is being used to store a series of .gif images and I'm just trying to test everything out by using document.getElementById to change the .src value but when I change it and load the page the image stays the same as it was before.
function setImage()
{
var images = new Array();
images[0] = anemone.gif;
images[1] = ball.gif;
images[2] = crab.gif;
images[3] = fish2.gif;
images[4] = gull.gif;
images[5] = jellyfish.gif;
images[6] = moon.gif;
images[7] = sail.gif;
images[8] = shell.gif;
images[9] = snail.gif;
images[10] = sun.gif;
images[11] = sunnies.gif;
images[12] = whale.gif;
var slots = new Array();
slots[0] = document.getElementById("slot" + 0);
slots[1] = document.getElementById("slot" + 1);
slots[2] = document.getElementById("slot" + 2);
slots[0].src = "snail.gif";
document.getElementById('slot0').src = images[0];
alert(images.length);
}
I can't understand why the image wont change, but I know it has to be something very simple. I've been wasting hours trying to get this one thing to change but nothing works. can anyone please point out the error of my ways?
There are a couple of issues with your code:
Your filenames need to be Strings, so they'll have to be quoted (also you can simplify the Array creation):
var images = ['anemone.gif', 'ball.gif', 'crab.gif', 'fish2.gif', 'gull.gif', 'jellyfish.gif', 'moon.gif', 'sail.gif', 'shell.gif', 'snail.gif', 'sun.gif', 'sunnies.gif', 'whale.gif'];
Also make sure you are getting your slot-elements right, quote all the attributes like:
<img id="slot0" class="slot" src="crab.gif" width="120" height="80">
When you create the slots-Array you can do it like this (no need to concat the ID string):
var slots = [document.getElementById('slot0'), document.getElementById('slot1'), document.getElementById('slot2')];
Finally make sure you call your function when the document has loaded / the DOM is ready. If you don't want to use a framework like jQuery your easiest bet is probably still using window.onload:
window.onload = setImage; //note that the parens are missing as you want to refer to the function instead of executing it
Further reading on Arrays, window.onload and DOMReady:
https://developer.mozilla.org/de/docs/DOM/window.onload
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array
javascript domready?