I just can't grasp the core concept I guess.
I have a page with contact list. But user can visit the page with contactID in URL, and in this case I should open a popup window with contact info. So, what am I doing:
My store looks like:
{
isRequested: boolean, // is initial loading done
isLoading: boolean, // is currently loading,
contacts: {[ key: number ]: IContact}
// where key is contact ID and IContact is generic a model
}
Open the page and start loading contacts list.
Without waiting for contacts I'm openinig the popup window.
So, how should I handle this situation? The only thing that come to my mind is to create dummy contact if it's not exist in store. Something like:
{
...,
contacts: {
1: {
isRequested: false,
isLoading: true,
isCorrupted: false // in case of bad ID
}
}
}
And subscribe to state change in popup window, to wait for this contact isRequested to change.
And when all contacts loaded - add all contacts without this one, or merge them.
Is it right approach, or is it a better solution do exists?
I would keep contacts empty and simply display a loading screen until the full list is loaded.
So your state looks like
{
isLoading: boolean, // is currently loading,
contacts: {[ key: number ]: IContact}
}
and the initial state
{
isLoading: false,
contacts: {}
}
In your view:
If contacts is an empty object, display nothing.
On first load (componentWillMount in React), load the list of contacts and set isLoading to true.
When isLoading is true, display a loading screen.
When the list is ready, display your information
Related
I need to be able to extend the collection that accounts-ui creates by default of users.
import { Class } from 'meteor/jagi:astronomy';
import { Behavior } from 'meteor/jagi:astronomy-softremove-behavior';
/**
* #class User
*/
const User = Class.create ({
name: 'User',
collection: Meteor.Users,
secured: false,
fields: {
emails: {
type: Email,
optional: true
}
},
behaviors: {
softremove: {
removedFieldName: 'removed',
hasRemovedAtField: true,
removedAtFieldName: 'removedAt'
},
timestamp: {
hasCreatedField: true,
createdFieldName: 'createdAt',
hasUpdatedField: true,
updatedFieldName: 'updatedAt'
}
}
});
export default User;
I wan't use Meteor.user.(), only like to use the User class.
Welcome to Stack Overflow.
The users collection in Meteor is special. Under the hood in Mongo it is a regular collection.
For security reasons, you can't use the users collection like a regular collection, for example you can update your own user record, but you can't browse and edit other users records.
My advice is to create a separate collection for your user profile information. It's not a perfect solution, but it is better to leave the users collection under the control of the Meteor accounts package.
It is possible to extend the collection to store a few profile type data elements, but even that has some wrinkles, because when a user record is updated, the tracker runs, and your UI will refresh (with possible negative side effects if the refresh isn't expected)
Below a typical action to test if a sap.m.Select contains an item with the name xyz and then select it.
success: function(oSelect) {
var oItems = oSelect.getItems();
$.each(oItems, function(i,v) {
if(oItems[i].getText() === "TestItemNameILikeToSelect") {
oTestOpa5TestItem = oItems[i];
}
});
if(oTestOpa5TestItem !== null) {
oSelect.setSelectedKey(oTestOpa5TestItem.getKey());
oTestOpa5TestItem.$().trigger("tap");
}
},
When I start the test run it does correctly select the proper item from the list and sets it visibly in the browser, but it does not trigger the attached event that is behind (e.g. change="onListItemChanged").
My application works fine, but I don't find a way to create a working test for it.
Thanks in advance
OPA5 has an 'Action' interface and two default implementations e.g. 'EnterText' and 'Press'. The recommended usage is to define an action block on the waitFor() options like this:
When.waitFor({
id: "myButton",
actions: new Press()
});
What you use is the 'old way' but it has some shortcomings:
success block is not synchronized with XHR requests but action is.
Sending a click/tap event to a control could require selecting some internal element. Imagine a click to nav container - there are several places you could click actually. Actions handle those details and define a standard behavior you could depend on.
It is better to encapsulate your selection logic inside a matchers block and even abstract it to a custom matcher. This way your success block will be cleaner and you could reuse the matcher in several places in your test.
OPA5 Actions
Have a look at the official UI5 Demo Kit, under samples > OPA5 > Code: Simulating user interactions on UI5 controls with OPA5, You will be able to find numerous examples of OPA 5 testing regarding general user interactions. In your case for the select:
opaTest("Should select an item in a sap.m.Select", function(Given, When, Then) {
When.waitFor({
id: "mySelect",
actions: new Press(),
success: function(oSelect) {
this.waitFor({
controlType: "sap.ui.core.Item",
matchers: [
new Ancestor(oSelect),
new Properties({ key: "Germany"})
],
actions: new Press(),
success: function() {
Opa5.assert.strictEqual(oSelect.getSelectedKey(), "Germany", "Selected Germany");
},
errorMessage: "Cannot select Germany from mySelect"
});
},
errorMessage: "Could not find mySelect"
});
});
https://sapui5.hana.ondemand.com/#/entity/sap.ui.test.Opa5/sample/sap.ui.core.sample.OpaAction/code/Opa.js
I need to simulate a drag & drop on fullcalendar in the week view with protractor. I found something with coordinates but I'd like a "no browser window dependent solution"... ther's also no way out on finding the exact starting cell in the week view by class or id ...or at least, I couldn't figure how to select a single cell of a row of a day because, using the Chrome's item selector, it seems every row has the same class fc-widget-content and cells are not "selectable" elements.
Are there any other chances?
maybe this is a little bit helpful (also very later ;). I also want to test my app with FullCalendar, but I'm using Cypress (similar to Protractor).
We plan items from an external list and assign it to a resource on a certain day/time in the FullCalendar (we use the scheduler plugin).
I found out that the drag and drop event is somehow intercepted by code, enriching it with for example properties of the event (like date, title and others). How I enriched this data is in the Cypress trigger('drop', data) command. Data is the evenData that is set by the Draggable class:
// Executed on the external list items, where every item we want to plan has class `.fc-event`.
this.draggableContainer = new Draggable(this.containerEl.nativeElement, {
itemSelector: '.fc-event',
eventData(eventEl) {
const id = eventEl.dataset.id;
return {
duration,
id: currentWorkItem.id,
title: currentWorkItem.description,
extendedProps: {
duration,
customRender: true,
data: currentWorkItem,
},
};
}
Then, in your test file (Cypress)
const eventData = {
date: new Date(),
dateStr: new Date().toISOString(),
draggedEl: {
dataset: {
notificationId: '123',
priority: '0',
title: 'Test',
},
},
jsEvent: null,
resource: {
id: '123',
},
event: null,
oldEvent: null,
};
cy.get('.fc-event') // selector for the external event I want to drag in the calendar
.trigger('dragstart')
.get('.fc-time-grid table tr[data-time="07:00:00"] td.fc-widget-content:nth-child(2)') // selector for where I want to drop the event.
.trigger('drop', eventData) // this will fire the eventDrop event
So, .trigger('drop', eventData) will fill the eventDrop info. It is not exactly the same as doing it manually, but works for me.
Caveats:
I haven't found a way to plan it on another resource (we use the resource scheduling plugin of FullCalendar.io). It does not matter that much, because you can specify it in the evenData (resource: { id: 'my-resource-id' } }.
No visual feedback because the drag mirror is not shown. Not a big problem during e2e testing, but it is a bit of a blackbox now. Maybe this is fixable
I am working on a page having lot of input-controls and related divs. There are use-cases on this page where I am suppossed to show/hide the divs depending on the order of user clicking on input-controls in various follow-up screens.
Now the divs are all there in first load itself and by showing/hiding, the screen changes for the user. Now to show/hide I can use css and add view* class to .main content div depending on business logic.
ex.:
.main div{
display: none;
}
.main.view1 div.a,.main.view1 div.b,.main.view1 div.f{
display:block;
}
.main.view2 div.c,.main.view2 div.f {
display:block;
}
.main.view3 div.c,.main.view3 div.f {
display:block;
}
....etc
But this way the no. of css classes are getting unmanageable.
Please suggest if there is a better method I can use wherein it becomes easy to manage the user-flows. I think there are regions in marionette which can help me manage this. Please suggest the best way and elaborate if the answer is marionette.regions
You can model the application as a state machine to model complicated workflows.
To define a state machine:
Define all the states that your application can be in.
Define the set of actions that are allowed in each state. Each action will transition the state of the application from one state to another.
Write the business logic for each action which includes both persisting changes to the server and also changing the state of the views accordingly.
This design is similar to creating a DFA, but you can add extra behaviour according to your needs.
If this sounds too abstract, here's an example of a simple state machine.
Let's say you're building a simple login application.
Design the States and Actions
INITIAL_STATE: The user visits the page for the first time and both fields are empty. Let's say you only want to make the username visible, but not the password in this state. (Similar to the new Gmail workflow)
USERNAME_ENTRY_STATE: When the user types in the username and hits return, in this state, you want to display the username and hide the password. You can have onUsernameEntered as an action in this state.
PASSWORD_ENTRY_STATE: Now, the username view will be hidden and the password view will be shown. When the user hits return, you have to check if the usernames and passwords match. Let's call this action onPasswordEntered
AUTHENTICATED_STATE: When the server validates the username/password combination, let's say you want to show the home page. Let's call this action onAuthenticated
I have omitted handling the Authentication Failed case for now.
Design the Views:
In this case, we have the UsernameView and the PasswordView
Design the Models:
A single Auth model suffices for our example.
Design the Routes:
Check out the best practices for handling routes with Marionette. The state machine should be initialized in the login route.
Sample Pseudo-Code:
I've only shown the code relevant to managing the state machine. Rendering and event handling can be handled as usual;
var UsernameView = Backbone.View.extend({
initialize: function(options) {
this.stateMachine = options.stateMachine;
},
onUserNameEntered: function() {
username = //get username from DOM;
this.stateMachine.handleAction('onUserNameEntered', username)
},
show: function() {
//write logic to show the view
},
hide: function() {
//write logic to hide the view
}
});
var PasswordView = Backbone.View.extend({
initialize: function(options) {
this.stateMachine = options.stateMachine;
},
onPasswordEntered: function() {
password = //get password from DOM;
this.stateMachine.handleAction('onPasswordEntered', password)
},
show: function() {
//write logic to show the view
},
hide: function() {
//write logic to hide the view
}
});
Each state will have an entry function which will initialize the views and and exit function which will cleanup the views. Each state will also have functions corresponding to the valid actions in that state. For example:
var BaseState = function(options) {
this.stateMachine = options.stateMachine;
this.authModel = options.authModel;
};
var InitialState = BaseState.extend({
entry: function() {
//show the username view
// hide the password view
},
exit: function() {
//hide the username view
},
onUsernameEntered: function(attrs) {
this.authModel.set('username', attrs.username');
this.stateMachine.setState('PASSWORD_ENTRY_STATE');
}
});
Similarly, you can write code for other states.
Finally, the State Machine:
var StateMachine = function() {
this.authModel = new AuthModel;
this.usernameView = new UserNameView({stateMachine: this});
//and all the views
this.initialState = new InitialState({authModel: this.authModel, usernameView: this.usernameView});
//and similarly, all the states
this.currentState = this.initialState;
};
StateMachine.prototype = {
setState: function(stateCode) {
this.currentState.exit(); //exit from currentState;
this.currentState = this.getStateFromStateCode(stateCode);
this.currentState.entry();
},
handleAction: function(action, attrs) {
//check if action is valid for current state
if(actionValid) {
//call appropriate event handler in currentState
}
}
};
StateMachine.prototype.constructor = StateMachine;
For a simple application this seems to be an overkill. For complicated business logic, it is worth the effort. This design pattern automatically prevents cases such as double-clicking on a button, since you would have already moved on to the next state and the new state does not recognise the previous state's action.
Once you have built the state machine, other members of your team can just plug in their states and views and also can see the big picture in a single place.
Libraries such as Redux do some of the heavy-lifting shown here. So you may want to consider React + Redux + Immutable.js as well.
I want to redraw a line in a line chart without reloading it (neither template nor controller) completely when navigating from country/5 to country/7. Can this be done with ui-router?
State
country/:id
Template with directive - country.html
<lineChart data="scope.someData">
Controller
onStateParamsChange => fetch data, set scope.someData
As of today, there is no official support for what you're looking for, which in UI Router parlance is considered 'dynamic parameters'. However, if you check out this experimental branch and help us out by testing and providing feedback, it will get merged to master sooner.
Set up your route/state like so:
$stateProvider.state("country", {
url: "/country/{id:int}",
params: { id: { dynamic: true } }
/* other state configuration here */
});
Then, in your controller, you can observe changes to id like so:
$stateParams.$observe("id", function(val) {
// val is the updated value of $stateParams.id
// Here's where you can do your logic to fetch new data & update $scope
});