I'm trying to pass a function to be called inside the nightmarejs evaluate statement and return a value. Something like this:
var thisfunction = function() {
var tags = [];
var list = document.querySelector('#selectSomething');
list.forEach(function(item) {
tags.push({
name: item.attributes[1].value,
id: item.attributes[2].value
})
});
return tags;
};
return nightmare
.goto('url')
.wait('#something')
.evaluate(function(getValues) {
getValues();
}, thisfunction)
.then(function(list) {
console.log(list);
})
I'm getting ReferenceError: getValues is not defined. Tried different approaches with return statements in various place, but no luck.
How can this be done?
Thanks!
I suppose that you can simply write the following code:
var thisfunction = function() {
var tags = [];
var list = document.querySelector('#selectSomething');
list.forEach(function(item) {
tags.push({
name: item.attributes[1].value,
id: item.attributes[2].value
})
});
return tags;
};
return nightmare
.goto('url')
.wait('#something')
.evaluate(thisfunction)
.then(function(list) {
console.log(list);
})
Reason Behind the Behaviour
You can pass strings/numbers as parameters but not functions/object as they serialised before passing to the evaluate method.
You can checkout the explaination here
Also there is ticket on Github of users with similar experiences.
Related
In my Ionic app I've added the plugin 'ngStorage' and it comes with a little demo code:
var add = function (thing) {
$localStorage.things.push(thing);
}
This works exactly as told. I add("foo") it, and do getAll() and the value is there. I remove the add(), but keep the getAll(), I still have the value "foo" (as expected).
This isn't very usefull for me, I want to access it with keys, so I've made the following:
var addByKey = function (key, value) {
$localStorage.things[key] = value;
// Or, I've also tried:
$localStorage.things.key = value;
}
When I do the addByKey("foo","bar") and then the getAll() I get the values exactly as I want. When I remove the addByKey() and reload, I expect it to still remember the set information, but it doesn't exist. However, the first attempt via the add() function still exists, "foo" is still there (meaning the array doesnt reset).
How do I make a key->value type of structure?
In case it's usefull:
.factory ('StorageService', function ($localStorage) {
$localStorage = $localStorage.$default({
things: []
});
var _getAll = function () {
return $localStorage.things;
};
var _add = function (thing) {
$localStorage.things.push(thing);
}
var _addByKey = function (thing, value) {
$localStorage.things[key] = value;
// Or, I've also tried:
$localStorage.things.key = value;
}
return {
getAll: _getAll,
add: _add,
addByKey: _addByKey
};
})
Assuming that you want a key value storage system you can simply use an object instead of an array so that every key can be set as a property of this object.
.factory('StorageService', function($localStorage) {
$localStorage = $localStorage.$default({
things: {}
});
var _getAll = function() {
return $localStorage.things;
};
var _addByKey = function(thing, value) {
$localStorage.things[thing] = value;
}
return {
getAll: _getAll,
addByKey: _addByKey
};
})
However, assuming that you want to keep a reference of all values on the main collection and access them through keys, you can consider using an object to store the things intead of an array. So that you can use a property to store all items (you can store in a different place as well) and use this object to store your keys by referencing the to a desired value on your collection.
You may need to implement the deletion logic to maintain the consistence between the collection and the dictionary.
Your factory would look like this:
.factory('StorageService', function($localStorage) {
$localStorage = $localStorage.$default({
things: {
items: []
}
});
var _getAll = function() {
return $localStorage.things.items;
};
var _add = function(thing) {
$localStorage.things.items.push(thing);
}
var _addByKey = function(thing, value) {
var i = $localStorage.things.items.push(value) - 1;
$localStorage.things[thing] = $localStorage.things.items[i];
}
return {
getAll: _getAll,
add: _add,
addByKey: _addByKey
};
})
I'm creating a grouped list of my pupils as per this example: http://msdn.microsoft.com/en-us/library/windows/apps/hh465464.aspx
The actual code for creating the grouping as well as the HTML are almost identical (safe for name changes).
I'm then push()ing some items into the original List() which then also updates the GroupedList(). That part works fine.
However, what I'm seeing is this:
This list should be grouped by firstnames (on display is "Lastname, Firstname"). What I'm seeing here is that item #1 should be in "S", #3 should be in "A" and #6 should be in "I".
The only thing that I'm doing different from the example is the DataSource, insofar as I'm push()ing an actual WinJS Class in there (with getter and setter functions for the attributes displayed in the List).
However, the getGroupKey(dataItem) and other grouping functions are working as they should, i.e. return the proper values.
Any ideas? Because otherwise I'd have to look at using two arrays (one being the List() and another the array where the class instances live) for which I'd then have to program sync routines to keep the data consistent and that's something I actually wanted to escape from...
Code follows below, relevant snippets only.
Defining the Lists and grouping functions:
function compareGroups(leftKey, rightKey) {
return leftKey.charCodeAt(0) - rightKey.charCodeAt(0);
}
function getGroupKey(dataItem) {
return dataItem.lastname.toUpperCase().charAt(0);
}
function getGroupData(dataItem) {
return {
title: dataItem.lastname.toUpperCase().charAt(0)
};
}
var pupilsList = new WinJS.Binding.List({ binding: true });
var groupedPupilsList = pupilsList.createGrouped(getGroupKey, getGroupData, compareGroups);
Where the Data comes from:
var Schueler = WinJS.Class.define(function (original, id, firstname, lastname, tutor_id, picture, email, phone, notes, birthday, classes) {
var that = this;
this._classnames = new Array();
if (original) {
[... irrelevant part snipped ...]
});
} else {
var row = id;
this._id = row.rowid;
this._firstname = row.firstname_enc;
this._lastname = row.lastname_enc;
this._tutor_id = row.tutor_id;
this._picture = row.picture_enc;
this._email = row.email_enc;
this._phone = row.phone_enc;
this._notes = row.notes_enc;
this._birthday = row.birthday_enc;
this._guid = row.guid;
this.updateClassnames();
}
},
{
id: {
get: function () {
return this._id;
},
set: function (id) {
this._id = id;
}
},
firstname: {
get: function () {
return this._firstname;
},
set: function () {
//TODO
}
},
lastname: {
get: function () {
return this._lastname;
},
set: function () {
//TODO
}
},
[... irrelevant parts snipped ...]
classnames: {
get: function () {
return this._classnames.join(", ");
},
set: function (names) {
this._classnames = names;
}
},
updateClassnames: function () {
var that = this;
SQLite3JS.openAsync(DataLayer.db_path)
.then(function (db) {
var sql = "SELECT Classes.name_enc FROM Classes JOIN Classes_Pupils ON Classes.rowid = Classes_Pupils.class_id JOIN Pupils ON Classes_Pupils.pupil_id = Pupils.rowid WHERE Pupils.rowid = {0};".format(that._id);
return db.allAsync(sql)
.then(function (results) {
db.close();
var names = new Array();
for (var i = 0; i < results.length; i++) {
names.push(results[i].name_enc.toString().decrypt());
}
that.classnames = names;
DataLayer.PupilsList.dispatchEvent("reload");
}, function (error) {
if (error.message.indexOf("database is locked") > -1) {
console.log("DB locked, will try again in 50 ms");
window.setTimeout(that.updateClassnames(), 50);
}
});
});
}
},
{
reconstructAll: function () {
DataLayer.retrieveSeveralRows("Pupils", function (results) {
for (var i = 0; i < results.length; i++) {
DataLayer.PupilsList.push(new Schueler(false, results[i]));
}
});
}
});
WinJS.Namespace.define("DataLayer", {
Schueler: Schueler
});
Workflow is as follows: First empty lists are created, then another routine checks for DB availability. As soon as that routine gives a green light, Schueler.reconstructAll() is called.
DataLayer.retrieveSeveralRows(table, callback) is a wrapper function for a call to the SQLite database, essentially doing a SELECT * FROM Pupils and returning the results to the callback function.
This callback then creates a new instance of the Schueler() class and pushes that to the list.
Addendum: If I use createSorted() everything is just dandy. Will use that for now.
Edit: As suggested by Kraig Brockschmidt, it seems to have indeed been a localization issues, so adding one line and modifying one function as follows fixes everything right up:
var charGroups = Windows.Globalization.Collation.CharacterGroupings();
function getGroupKey(dataItem) {
return charGroups.lookup(dataItem.lastname.toUpperCase().charAt(0));
}
I see that you're working with createSorted now, but there are a couple of other things you can do to diagnose the original issue.
First, try using some static data instead of populating your list dynamically.
Second, put some console.log output inside your getGroupKey and getGroupData functions so you can evaluate what you're returning, exactly.
The other thing I should mention is that the MSDN docs page shows code that isn't sensitive to all local languages. That is, using the first character of a string for sort order isn't always the right thing. There is an API in Windows.Globalization.Collation (http://msdn.microsoft.com/en-us/library/windows/apps/windows.globalization.collation.aspx) that is built to handle sort ordering properly. If you look at the [HTML ListView Grouping and Semantic Zoom sample][1], in the file groupeddata.js, you'll see how this is used. Offhand this shouldn't affect your data, but I wanted to mention it.
I am stuck with the following situation. I have a select statement which uses a function in the current scope me. How do I go about putting me into the select function?
var me = this;
var results = Enumerable
.from(jsonData)
.select('x,i=>{abbr:me.transform(x), name:x}')
.toArray(); //me.transform(x) will hit error
'me' is an instance of a dynamically generated object, and me.transform(x) uses other dependencies in 'me' to work as well. That means I cannot make 'me.transform()' global function.
EDIT
var me = this;
var results = Enumerable
.from(jsonData)
.select(function(x,i){
return {abbr:me.transform(x), name:x};
}).toArray();
Actually, this modification will work, however, I would like to find out the how to make the shortcut syntax work.
What you could do is project your objects to a composite object containing both the item in the collection and the object you want to introduce into the query.
You can use this Capture function to capture the variables:
function Capture(bindings, name) {
var benumerable = Enumerable.From(bindings),
itemname = name || 'Item';
return function (e) {
return e.Select(function (item) {
return benumerable.Concat(Enumerable.Return({ Key: itemname, Value: item }))
.ToObject("$.Key", "$.Value");
});
};
}
Use it in a Let binding.
var query = Enumerable.From(data)
.Let(Capture({ Me: me }))
.Select("{ abbr: $.Me.transform($.Item), name: $.Item }")
.ToArray();
My bad. Is this what you mean?
var me = this;
var results = Enumerable
.from(jsonData)
.select('x,i=>{abbr:' + me.transform(x) + ', name:x}')
.toArray(); //me.transform(x) will hit error
I get undefined whenever I get the value of a property of an object.
function run(id){
var report = services.getReportInfo(id);
var childReport = {
id: newGuid(),
parentId: report.id, // i get undefined
reportPath: report.path // i get undefined
};
...
}
services.js
angular.module('project.services').factory('services', function(){
var reports = [
{
....
},
{
....
}
];
function getReportInfo(id){
var report = reports.filter(function(element){
return element.id === id;
});
};
return{
getReportInfo: getReportInfo
};
}
Whenever I put breakpoint on my var report = services.getReportInfo(id) it could contains the correct values for each property of the my report object. However, when I get the report.id or report.path, I get undefined value.
--Edited--
Oh, I know now where I got wrong.
The getReportInfo function returns an array and I'm accessing the properties without telling from what index should it get the values for the said properties.
function run(id){
var report = services.getReportInfo(id);
var childReport = {
id: newGuid(),
parentId: report[0].id,
reportPath: report[0].path
};
...
}
I placed static index 0, since I know that the array will always have a length of 1.
You are not returning anything from the .factory method and the getReportInfo is also not returning anything. For what you are trying to do, try to use .service method:
angular.module('project.services').service('services', function(){
var reports = [
{
....
},
{
....
}
];
this.getReportInfo = function (id){
var report = reports.filter(function(element){
return element.id === id;
});
return report;
}
}
Here is a good explanation on how to use .factory and .service:
Confused about Service vs Factory
Two immediate issues with the code I can see:
1) Your factory function needs to return a value or constructor function. Right now your code is not initializing the factory to any value.
2) Your getReportInfo function also doesn't return a value, yet you are assigning the function result to a variable.
Read more here: http://docs.angularjs.org/guide/dev_guide.services.creating_services
In Angular, I have in scope a object which returns lots of objects. Each has an ID (this is stored in a flat file so no DB, and I seem to not be able to user ng-resource)
In my controller:
$scope.fish = [
{category:'freshwater', id:'1', name: 'trout', more:'false'},
{category:'freshwater', id:'2', name:'bass', more:'false'}
];
In my view I have additional information about the fish hidden by default with the ng-show more, but when I click the simple show more tab, I would like to call the function showdetails(fish.fish_id).
My function would look something like:
$scope.showdetails = function(fish_id) {
var fish = $scope.fish.get({id: fish_id});
fish.more = true;
}
Now in the the view the more details shows up. However after searching through the documentation I can't figure out how to search that fish array.
So how do I query the array? And in console how do I call debugger so that I have the $scope object to play with?
You can use the existing $filter service. I updated the fiddle above http://jsfiddle.net/gbW8Z/12/
$scope.showdetails = function(fish_id) {
var found = $filter('filter')($scope.fish, {id: fish_id}, true);
if (found.length) {
$scope.selected = JSON.stringify(found[0]);
} else {
$scope.selected = 'Not found';
}
}
Angular documentation is here http://docs.angularjs.org/api/ng.filter:filter
I know if that can help you a bit.
Here is something I tried to simulate for you.
Checkout the jsFiddle ;)
http://jsfiddle.net/migontech/gbW8Z/5/
Created a filter that you also can use in 'ng-repeat'
app.filter('getById', function() {
return function(input, id) {
var i=0, len=input.length;
for (; i<len; i++) {
if (+input[i].id == +id) {
return input[i];
}
}
return null;
}
});
Usage in controller:
app.controller('SomeController', ['$scope', '$filter', function($scope, $filter) {
$scope.fish = [{category:'freshwater', id:'1', name: 'trout', more:'false'}, {category:'freshwater', id:'2', name:'bass', more:'false'}]
$scope.showdetails = function(fish_id){
var found = $filter('getById')($scope.fish, fish_id);
console.log(found);
$scope.selected = JSON.stringify(found);
}
}]);
If there are any questions just let me know.
To add to #migontech's answer and also his address his comment that you could "probably make it more generic", here's a way to do it. The below will allow you to search by any property:
.filter('getByProperty', function() {
return function(propertyName, propertyValue, collection) {
var i=0, len=collection.length;
for (; i<len; i++) {
if (collection[i][propertyName] == +propertyValue) {
return collection[i];
}
}
return null;
}
});
The call to filter would then become:
var found = $filter('getByProperty')('id', fish_id, $scope.fish);
Note, I removed the unary(+) operator to allow for string-based matches...
A dirty and easy solution could look like
$scope.showdetails = function(fish_id) {
angular.forEach($scope.fish, function(fish, key) {
fish.more = fish.id == fish_id;
});
};
Angularjs already has filter option to do this ,
https://docs.angularjs.org/api/ng/filter/filter
Your solutions are correct but unnecessary complicated. You can use pure javascript filter function. This is your model:
$scope.fishes = [{category:'freshwater', id:'1', name: 'trout', more:'false'}, {category:'freshwater', id:'2', name:'bass', more:'false'}];
And this is your function:
$scope.showdetails = function(fish_id){
var found = $scope.fishes.filter({id : fish_id});
return found;
};
You can also use expression:
$scope.showdetails = function(fish_id){
var found = $scope.fishes.filter(function(fish){ return fish.id === fish_id });
return found;
};
More about this function: LINK
Saw this thread but I wanted to search for IDs that did not match my search. Code to do that:
found = $filter('filter')($scope.fish, {id: '!fish_id'}, false);