I have a filter to convert content with id into user name. For instance, it converts Thank you #id-3124324 ! into Thank you #Jack !.
var filter = function (content) {
var re = /\s#(id\-\d+)\s/g;
var matches = [];
var lastMatch = re.exec(content);
while (lastMatch !== null) {
matches.push(lastMatch[1]); // uid being mentioned
lastMatch = re.exec(content);
}
// TODO: query user name from matched id
// replace id with user name
// fake usernames here
var usernames = ['Random Name'];
for (var i = 0; i < usernames.length; ++i) {
content = content.replace(new RegExp(matches[i], 'g'), usernames[i]);
}
return content;
};
Vue.filter('username', filter);
But in my case, usernames should be achieved with AJAX of a query with id. How should I do it?
Anything you can do with a filter you can do with a computed. In Vue 2.0, there won't be filters, so you'll need to use computeds instead.
Fetching data asynchronously into a computed is a somewhat messy problem, which is why there is the vue-async-computed plugin. The difficulty is that the computed has to return a value synchronously, when it hasn't finished fetching data.
The solution is to have the computed depend on a cache of fetched data. If the needed value isn't in the cache, the computed kicks off the async process to fetch it and returns some placeholder value. When the process adds a value to the cache, the computed should notice the change in the cache and return the complete value.
In the demo below, I had to make an auxiliary variable, trigger, which I reference in the computed just so I know there's been an update. The fetch process increments trigger, which triggers the computed to re-evaluate. The computed doesn't notice when values are added to or updated in decodedIds. There may be a better way to deal with that. (Using async computed should make it a non-issue.)
vm = new Vue({
el: 'body',
data: {
messages: [
'Thank you #id-3124324!'
],
decodedIds: {},
trigger: 0
},
computed: {
decodedMessages: function() {
return this.messages.map((m) => this.decode(m, this.trigger));
}
},
methods: {
decode: function(msg) {
var re = /#(id\-\d+)/g;
var matches = msg.match(re);
for (const i in matches) {
const p1 = matches[i].substr(1);
if (!(p1 in this.decodedIds)) {
// Indicate name is loading
this.decodedIds[p1] = '(...)';
// Mock up fetching data
setTimeout(() => {
this.decodedIds[p1] = 'some name';
++this.trigger;
}, 500);
}
}
return msg.replace(re, (m, p1) => this.decodedIds[p1]);
}
}
});
setTimeout(() => {
vm.messages.push('Added #id-12345 and #id-54321.');
}, 1500);
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<div v-for="message in decodedMessages">
{{message}}
</div>
Related
I have 3 separate js files, one js file has function that i export and access on the other two js file. The purpose of the function is to get data from realtime database firebase once, when the function is called for the first time it will get data from database and store it on array outside the function. Then on second call, it will just return the array that contains data from database.
let mySub = [];
export function getMySub(id) { // get users subjects
if (mySub.length !== 0) {
console.log('From Array SUBJECTS');
return mySub;
} else if (mySub.length == 0) {
get(child(path, "General")).then((snapshot) => {
snapshot.forEach(function(childSnapshot) {
var data = childSnapshot.val();
if (data.INS_SYS_ID == id) {
mySub.push([data.COURSE_NAME, data.SECTION, childSnapshot.key, data.ENROLLED_STUDENTS]);
}
})
}).catch((error) => {
console.error(error);
});
console.log('From DB SUBJECTS');
return mySub;
}
}
It works like this, if the mySub array length is not equal to zero the function will just return the array. if the array length is equal to zero it will get data from database and store it to mySub.
I also have a function on that same js file that updates the mySub array when i need to.
export function updateDisplayedSub(coursename, section, key, enrolledStudents) {
mySub.push([coursename, section, key, enrolledStudents]);
}
Now here is my problem, whenever i call the function that updates mySub array and call the function getMySub(id). it returns array that reads from database instead of returning the existing array that i updated.
updateDisplayedSub and getMySub is called on different js file, does this affect? I'm using this function to avoid too many reads and conserve bandwidth on my free tier hosting firebase.
It seems that you are trying to invent memoization.
Your current method falls short because if you vary the id parameter, there's no way of telling if the cached data in mySub refers to that id or not. This will likely cause bugs.
Instead, we can write a higher order function that returns a function that wraps your existing function:
const memoizeUnary = (f) => {
const map = new Map();
return (v, skipCached) => {
if (!skipCached && map.has(v)) {
return map.get(v);
}
else {
const r = f(v);
map.set(v, r);
return r;
}
};
};
and use it to wrap another function:
function foo(v) {
console.log("foo invoked");
return `hello ${v}`;
}
const memoizedFoo = memoizeUnary(foo);
Now, if we call
memoizedFoo("monkey")
for the first time, the function that it wraps will be invoked, its return value stored in a map and then returned to the caller.
The second time we call
memoizedFoo("monkey")
the function it wraps will not be called, and the cached return value for parameter "monkey" will be retrieved and returned to the caller.
If you want to reset the cache and fetch from the wrapped function again, you can add an optional parameter, true to skip the cache, re-fetch and store the new value in the cache:
memoizedFoo("monkey", true)
Pop open the snippet below to see this in action:
const memoizeUnary = (f) => {
const map = new Map();
return (v, skipCached) => {
if (!skipCached && map.has(v)) {
return map.get(v);
} else {
const r = f(v);
map.set(v, r);
return r;
}
};
};
function foo(v) {
console.log("foo invoked");
return `hello ${v}`;
}
const memoizedFoo = memoizeUnary(foo);
memoizedFoo("a");
memoizedFoo("a");
memoizedFoo("a");
memoizedFoo("a");
memoizedFoo("a");
memoizedFoo("a", true);
memoizedFoo("a");
memoizedFoo("a");
memoizedFoo("a");
Notice how, even though we call memoizedFoo("a") multiple times, the function it wraps is only called for the first time or if we supply a second true parameter.
I want to execute this query
select * from properties where propertyCode IN ("field1", "field2", "field3")
How can I achieve this in IndexedDB
I tried this thing
getData : function (indexName, params, objectStoreName) {
var defer = $q.defer(),
db, transaction, index, cursorRequest, request, objectStore, resultSet, dataList = [];
request = indexedDB.open('test');
request.onsuccess = function (event) {
db = request.result;
transaction = db.transaction(objectStoreName);
objectStore = transaction.objectStore(objectStoreName);
index = objectStore.index(indexName);
cursorRequest = index.openCursor(IDBKeyRange.only(params));
cursorRequest.onsuccess = function () {
resultSet = cursorRequest.result;
if(resultSet){
dataList.push(resultSet.value);
resultSet.continue();
}
else{
console.log(dataList);
defer.resolve(dataList);
}
};
cursorRequest.onerror = function (event) {
console.log('Error while opening cursor');
}
}
request.onerror = function (event) {
console.log('Not able to get access to DB in executeQuery');
}
return defer.promise;
But didn't worked. I tried google but couldn't find exact answer.
If you consider that IN is essentially equivalent to field1 == propertyCode OR field2 == propertyCode, then you could say that IN is just another way of using OR.
IndexedDB cannot do OR (unions) from a single request.
Generally, your only recourse is to do separate requests, then merge them in memory. Generally, this will not have great performance. If you are dealing with a lot of objects, you might want to consider giving up altogether on this approach and thinking of how to avoid such an approach.
Another approach is to iterate over all objects in memory, and then filter those that don't meet your conditions. Again, terrible performance.
Here is a gimmicky hack that might give you decent performance, but it requires some extra work and a tiny bit of storage overhead:
Store an extra field in your objects. For example, plan to use a property named hasPropertyCodeX.
Whenever any of the 3 properties are true (has the right code), set the field (as in, just make it a property of the object, its value is irrelevant).
When none of the 3 properties are true, delete the property from the object.
Whenever the object is modified, update the derived property (set or unset it as appropriate).
Create an index on this derived property in indexedDB.
Open a cursor over the index. Only objects with a property present will appear in the cursor results.
Example for 3rd approach
var request = indexedDB.open(...);
request.onupgradeneeded = upgrade;
function upgrade(event) {
var db = event.target.result;
var store = db.createObjectStore('store', ...);
// Create another index for the special property
var index = store.createIndex('hasPropCodeX', 'hasPropCodeX');
}
function putThing(db, thing) {
// Before storing the thing, secretly update the hasPropCodeX value
// which is derived from the thing's other properties
if(thing.field1 === 'propCode' || thing.field2 === 'propCode' ||
thing.field3 === 'propCode') {
thing.hasPropCodeX = 1;
} else {
delete thing.hasPropCodeX;
}
var tx = db.transaction('store', 'readwrite');
var store = tx.objectStore('store');
store.put(thing);
}
function getThingsWherePropCodeXInAnyof3Fields(db, callback) {
var things = [];
var tx = db.transaction('store');
var store = tx.objectStore('store');
var index = store.index('hasPropCodeX');
var request = index.openCursor();
request.onsuccess = function(event) {
var cursor = event.target.result;
if(cursor) {
var thing = cursor.value;
things.push(thing);
cursor.continue();
} else {
callback(things);
}
};
request.onerror = function(event) {
console.error(event.target.error);
callback(things);
};
}
// Now that you have an api, here is some example calling code
// Not bothering to promisify it
function getData() {
var request = indexedDB.open(...);
request.onsuccess = function(event) {
var db = event.target.result;
getThingsWherePropCodeXInAnyof3Fields(db, function(things) {
console.log('Got %s things', things.length);
for(let thing of things) {
console.log('Thing', thing);
}
});
};
}
I try to achieve the following: When count is changed to "2" I need the function to push the JSON, named "updates", to the specific place in database, and take names from PlayerQueue node (0:"Mik", 1:"Bg" etc.) and put it into the database as "id". So the thing is that I need it to take first two nodes (0 and 1 in this case) and take names out of it (Mik and Bg) and put them in the database as id1 and id2 (in this database I have only one id value but I will add it later), the issue is that I can't figure out how to take out names from the first two nodes.
My database:
And here is my code
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import { resolve } from 'url';
//Game/queue/{queueId}/PlayerCount
admin.initializeApp(functions.config().firebase);
exports.createGame = functions.database.ref('Game/queue/PlayerCount').onUpdate((change, context) => {
const ref1 = admin.database().ref('/Game/queue/PlayerQueue').limitToFirst(1);
var tmp:String = 'esh';
ref1.once("value")
.then(result => {
tmp = result.val();
console.log(tmp)
var updates = {};
updates['id'] = tmp
updates['visible'] = {
place: 'a1',
sign: 'rock'
};
const after = change.after.val();
if(after.count == 2){
return admin.database().ref('/Game/allGames').push(updates);
}
return null
}).catch(reason => {
console.log(reason)
});
return null;
});
Since you're looking for the first two child nodes in the queue, you should order by their ID and then limit to getting 2 children:
const query = admin.database().ref('/Game/queue/PlayerQueue').orderByKey().limitToFirst(2);
Then you can listen for the value:
query.once("value").then(snapshot => {
snapshot.forEach(child => {
console.log(child.key+": "+child.val());
});
});
The above purely solves the "getting the first two child nodes".
Update: to get the two children into separate variables, you can do something like this:
query.once("value").then(snapshot => {
var first, second;
snapshot.forEach(child => {
console.log(child.key+": "+child.val());
if (!first) {
first = child.val();
}
else if (!second) {
second = child.val();
}
});
if (first && second) {
// TODO: do something with first and second
}
});
Use Firestore unless you have a valid reason to use the Realtime Database.
See Cloud Firestore triggers documentation on how to take action .onUpdate to qty 2. See documentation example below
exports.updateUser = functions.firestore
.document('users/{userId}')
.onUpdate((change, context) => {
// Get an object representing the document
// e.g. {'name': 'Marie', 'age': 66}
const newValue = change.after.data();
// ...or the previous value before this update
const previousValue = change.before.data();
// access a particular field as you would any JS property
const name = newValue.name;
// perform desired operations ...
});
I have an application communicating with a device via serial port. Every sent command is answered by a data event containing the status/answer. Basically there are commands changing the device and a command which just returns the status. Every time the last command has been answered (so upon receiving data) the app should send the next command or as a default query the status. I'm trying to model this with rxjs.
My idea here is that there is a command observable and a data observable derived from the data event. These two should be combined in such a way, that the resulting observable only emits values, when there is data and combine it with a command or the default command (request status), if no command came down the command stream.
data: ---------d---d-----d---------d------d-------
command: --c1---c2----------------------c3-----------
______________________________________________________
combined ---------c1--c2----dc--------dc-----c3
dc is the default command. Also no commands should be lost.
Currently I have an implementation with a anonymous subject, implementing the observable and observer myself. Collecting commands from the command stream in an array, subscribing to the data event, publish the data by hand with onNext and sending the next command from the array or the default. This works, but I have the feeling this could be expressed more elegantly with rxjs.
One approach was to have a separate default_command stream, repeating the default command every 100ms. This was merged with the command stream and then zipped with the data stream. The problem here was the merged command stream, because it piled up default commands, but the default command should only apply, if there is no other command.
Only thing I can think of is to:
subscribe to the command stream and queue the results (in an array)
Apply a mapping operation to the data stream which will pull from the queue (or use default if the queue is empty).
We can wrap this up into a generic observable operator. I'm bad with names, so I'll call it zipWithDefault:
Rx.Observable.prototype.zipWithDefault = function(bs, defaultB, selector) {
var source = this;
return Rx.Observable.create(function(observer) {
var sourceSubscription = new Rx.SingleAssignmentDisposable(),
bSubscription = new Rx.SingleAssignmentDisposable(),
subscriptions = new Rx.CompositeDisposable(sourceSubscription, bSubscription),
bQueue = [],
mappedSource = source.map(function(value) {
return selector(value, bQueue.length ? bQueue.shift() : defaultB);
});
bSubscription.setDisposable(bs.subscribe(
function(b) {
bQueue.push(b);
},
observer.onError.bind(observer)));
sourceSubscription.setDisposable(mappedSource.subscribe(observer));
return subscriptions;
});
};
And use it like so:
combined = dataStream
.zipWithDefault(commandStream, defaultCommand, function (data, command) {
return command;
});
I think the sample operator would be your best bet. Unfortunately, it does not come with a built in default value, so you would have to roll your own from the existing operator:
Rx.Observable.prototype.sampleWithDefault = function(sampler, defaultValue){
var source = this;
return new Rx.AnonymousObservable(function (observer) {
var atEnd, value, hasValue;
function sampleSubscribe() {
observer.onNext(hasValue ? value : defaultValue);
hasValue = false;
}
function sampleComplete() {
atEnd && observer.onCompleted();
}
return new Rx.CompositeDisposable(
source.subscribe(function (newValue) {
hasValue = true;
value = newValue;
}, observer.onError.bind(observer), function () {
atEnd = true;
}),
sampler.subscribe(sampleSubscribe, observer.onError.bind(observer), sampleComplete)
);
}, source);
}
You can achieve the queuing behavior using the controlled operator. Thus your final data chain would look like so:
var commands = getCommandSource().controlled();
var pipeline = commands
.sampleWithDefault(data, defaultCommand)
.tap(function() { commands.request(1); });
Here is a full example:
Rx.Observable.prototype.sampleWithDefault = function(sampler, defaultValue) {
var source = this;
return new Rx.AnonymousObservable(function(observer) {
var atEnd, value, hasValue;
function sampleSubscribe() {
observer.onNext(hasValue ? value : defaultValue);
hasValue = false;
}
function sampleComplete() {
atEnd && observer.onCompleted();
}
return new Rx.CompositeDisposable(
source.subscribe(function(newValue) {
hasValue = true;
value = newValue;
}, observer.onError.bind(observer), function() {
atEnd = true;
}),
sampler.subscribe(sampleSubscribe, observer.onError.bind(observer), sampleComplete)
);
}, source);
}
var scheduler = new Rx.TestScheduler();
var onNext = Rx.ReactiveTest.onNext;
var onCompleted = Rx.ReactiveTest.onCompleted;
var data = scheduler.createHotObservable(onNext(210, 18),
onNext(220, 17),
onNext(230, 16),
onNext(250, 15),
onCompleted(1000));
var commands = scheduler.createHotObservable(onNext(205, 'a'),
onNext(210, 'b'),
onNext(240, 'c'),
onNext(400, 'd'),
onCompleted(800))
.controlled(true, scheduler);
var pipeline = commands
.sampleWithDefault(data, 'default')
.tap(function() {
commands.request(1);
});
var output = document.getElementById("output");
pipeline.subscribe(function(x) {
var li = document.createElement("li");
var text = document.createTextNode(x);
li.appendChild(text);
output.appendChild(li);
});
commands.request(1);
scheduler.start();
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/2.5.2/rx.all.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/2.5.2/rx.testing.js"></script>
<div>
<ul id="output" />
</div>
This can be solved by using the scan function. In the accumulated value the commands are stored for which no data response has been received yet.
var result = Rx.Observable
.merge(data, command)
.scan(function (acc, x) {
if (x === 'd') {
acc.result = acc.commands.length > 0 ? acc.commands.shift() : 'dc';
} else {
acc.result = '';
acc.commands.push(x);
}
return acc;
}, {result: '', commands: []})
.map(function (x) {
return x.result;
})
.filter(function (x) {
return x !== '';
});
Please find a complete more detail here: http://jsbin.com/tubade/edit?html,js,console
What I have is simple CRUD operation. Items are listed on page, when user clicks button add, modal pops up, user enters data, and data is saved and should automatically (without refresh)be added to the list on page.
Service:
getAllIncluding: function(controllerAction, including) {
var query = breeze.EntityQuery.from(controllerAction).expand(including);
return manager.executeQuery(query).fail(getFailed);
},
addExerciseAndCategories: function(data, initialValues) {
var addedExercise = manager.createEntity("Exercise", initialValues);
_.forEach(data, function(item) {
manager.createEntity("ExerciseAndCategory", { ExerciseId: addedExercise._backingStore.ExerciseId, CategoryId: item.CategoryId });
});
saveChanges().fail(addFailed);
function addFailed() {
removeItem(items, item);
}
},
Controller:
$scope.getAllExercisesAndCategories = function() {
adminCrudService.getAllIncluding("ExercisesAndCategories", "Exercise,ExerciseCategory")
.then(querySucceeded)
.fail(queryFailed);
};
function querySucceeded(data) {
$scope.queryItems = adminCrudService.querySucceeded(data);
var exerciseIds = _($scope.queryItems).pluck('ExerciseId').uniq().valueOf();
$scope.exerciseAndCategories = [];
var createItem = function (id, exercise) {
return {
ExerciseId: id,
Exercise : exercise,
ExerciseCategories: []
};
};
// cycle through ids
_.forEach(exerciseIds, function (id) {
// get all the queryItems that match
var temp = _.where($scope.queryItems, {
'ExerciseId': id
});
// go to the next if nothing was found.
if (!temp.length) return;
// create a new (clean) item
var newItem = createItem(temp[0].ExerciseId, temp[0].Exercise);
// loop through the queryItems that matched
_.forEach(temp, function (i) {
// if the category has not been added , add it.
if (_.indexOf(newItem.ExerciseCategories, i.ExerciseCategory) < 0) {
newItem.ExerciseCategories.push(i.ExerciseCategory);
}
});
// Add the item to the collection
$scope.items.push(newItem);
});
$scope.$apply();
}
Here is how I add new data from controller:
adminCrudService.addExerciseAndCategories($scope.selectedCategories, { Name: $scope.NewName, Description: $scope.NewDesc });
So my question is, why list isn't updated in real time (when I hit save I must refresh page).
EDIT
Here is my querySuceeded
querySucceeded: function (data) {
items = [];
data.results.forEach(function(item) {
items.push(item);
});
return items;
}
EDIT 2
I believe I've narrowed my problem !
So PW Kad lost two hours with me trying to help me to fix this thing (ad I thank him very very very much for that), but unfortunately with no success. We mostly tried to fix my service, so when I returned to my PC, I've again tried to fix it. I believe my service is fine. (I've made some changes as Kad suggested in his answer).
I believe problem is in controller, I've logged $scope.items, and when I add new item they don't change, after that I've logged $scope.queryItems, and I've noticed that they change after adding new item (without refresh ofc.). So probably problem will be solved by somehow $watching $scope.queryItems after loading initial data, but at the moment I'm not quite sure how to do this.
Alright, I am going to post an answer that should guide you on how to tackle your issue. The issue does not appear to be with Breeze, nor with Angular, but the manner in which you have married the two up. I say this because it is important to understand what you are doing in order to understand the debug process.
Creating an entity adds it to the cache with an entityState of isAdded - that is a true statement, don't think otherwise.
Now for your code...
You don't have to chain your query execution with a promise, but in your case you are returning the data to your controller, and then passing it right back into some function in your service, which wasn't listed in your question. I added a function to replicate what yours probably looks like.
getAllIncluding: function(controllerAction, including) {
var query = breeze.EntityQuery.from(controllerAction).expand(including);
return manager.executeQuery(query).then(querySucceeded).fail(getFailed);
function querySucceeded(data) {
return data.results;
}
},
Now in your controller simply handle the results -
$scope.getAllExercisesAndCategories = function() {
adminCrudService.getAllIncluding("ExercisesAndCategories", "Exercise,ExerciseCategory")
.then(querySucceeded)
.fail(queryFailed);
};
function querySucceeded(data) {
// Set your object directly to the data.results, because that is what we are returning from the service
$scope.queryItems = data;
$scope.exerciseAndCategories = [];
Last, let's add the properties we create the entity and see if that gives Angular a chance to bind up properly -
_.forEach(data, function(item) {
var e = manager.createEntity("ExerciseAndCategory");
e.Exercise = addedExercise; e.Category: item.Category;
});
So I've managed to solve my problem ! Not sure if this is right solution but it works now.
I've moved everything to my service, which now looks like this:
function addCategoriesToExercise(tempdata) {
var dataToReturn = [];
var exerciseIds = _(tempdata).pluck('ExerciseId').uniq().valueOf();
var createItem = function (id, exercise) {
return {
ExerciseId: id,
Exercise: exercise,
ExerciseCategories: []
};
};
// cycle through ids
_.forEach(exerciseIds, function (id) {
// get all the queryItems that match
var temp = _.where(tempdata, {
'ExerciseId': id
});
// go to the next if nothing was found.
if (!temp.length) return;
// create a new (clean) item
var newItem = createItem(temp[0].ExerciseId, temp[0].Exercise);
// loop through the queryItems that matched
_.forEach(temp, function (i) {
// if the category has not been added , add it.
if (_.indexOf(newItem.ExerciseCategories, i.ExerciseCategory) < 0) {
newItem.ExerciseCategories.push(i.ExerciseCategory);
}
});
// Add the item to the collection
dataToReturn.push(newItem);
});
return dataToReturn;
}
addExerciseAndCategories: function (data, initialValues) {
newItems = [];
var addedExercise = manager.createEntity("Exercise", initialValues);
_.forEach(data, function (item) {
var entity = manager.createEntity("ExerciseAndCategory", { ExerciseId: addedExercise._backingStore.ExerciseId, CategoryId: item.CategoryId });
items.push(entity);
newItems.push(entity);
});
saveChanges().fail(addFailed);
var itemsToAdd = addCategoriesToExercise(newItems);
_.forEach(itemsToAdd, function (item) {
exerciseAndCategories.push(item);
});
function addFailed() {
removeItem(items, item);
}
}
getAllExercisesAndCategories: function () {
var query = breeze.EntityQuery.from("ExercisesAndCategories").expand("Exercise,ExerciseCategory");
return manager.executeQuery(query).then(getSuceeded).fail(getFailed);
},
function getSuceeded(data) {
items = [];
data.results.forEach(function (item) {
items.push(item);
});
exerciseAndCategories = addCategoriesToExercise(items);
return exerciseAndCategories;
}
And in controller I have only this:
$scope.getAllExercisesAndCategories = function () {
adminExerciseService.getAllExercisesAndCategories()
.then(querySucceeded)
.fail(queryFailed);
};
function querySucceeded(data) {
$scope.items = data;
$scope.$apply();
}