How to split a view file in Sproutcore? - view

Suppose you have a SC.SplitView to implement a master-detail functionality, right now I would like to have one developer to work on customizing the SC.ViewList of the master piece, while have another to work on customizing and usability of the SC.View of the detail.
As the page is growing more and more I was wondering if there would be a known practice on how to split the file in two having the SC.View master-child in one file and the SC.View detail-child in another.
Has anyone run into this need?

You can easily split your views into multiple files and this is highly encouraged!
The main premise is that you will have three views:
The SC.SplitView
The left view, and
The right view
You'll need to use sc_require so that the SplitView can find the others. Here's a quick example:
# Inside my_app/resources/main_page.js
sc_require('views/left_split_panel')
sc_require('views/right_split_panel')
SplitView.extend({
childViews: ['leftPanel', 'rightPanel'],
leftPanel: MyApp.LeftSplitPanelView.extend(SC.SplitChild, {
minimumSize: 200
}),
rightPanel: MyApp.RightSplitPanelView.extend(SC.SplitChild, {
autoResizeStyle: SC.RESIZE_AUTOMATIC
})
})
Then, the other two views:
#inside my_app/views/left_split_panel.js
MyApp.LeftSplitPanelView = SC.View.extend({
childViews: ['someView anotherView'],
someView: SC.View.extend(...),
anotherView: SC.View.extend(...)
})
and
#inside my_app/views/right_split_panel.js
MyApp.RightSplitPanelView = SC.View.extend({
childViews: ['dudeView sweetView'],
dudeView: SC.View.extend(...),
sweetView: SC.View.extend(...)
})
Checkout the "Separating Views" section of the second Getting Started guide for more info and perhaps a better example (quick note: SC.View.design() and SC.View.extend() are almost identical, but .design() has been deprecated; we're in the process of updating the guides to match best practices).

Related

Overriding cy.get with a custom command to default to using data-cy?

I'm trying to use data-cy as much as I can in my code.
It's slightly tedious having to write cy.get('[data-cy=name]') all the time.
Is it possible to create a custom command that would by default try and find a data-cy first.
So if I wrote cy.get('name') it would try and find data-cy="name" on the page, if I used cy.get('.class') it would try and find a class with class on the page, if I used cy.get('#id') it would try and find an id with 'id` on the page?
Basically, I just want cy.get() to default to trying to find data-cy first and then work as it originally does if I pass in anything else to it.
It's a nifty idea, but you are leaning towards conditional testing if you try to do all-in-one.
I would have a custom command for data-cy and stick with cy.get() for the other selectors
Cypress.Commands.add('attr', { prevSubject: false }, (attr) => {
return cy.get(`[data-cy="${attr}"]`)
})
cy.attr('name')
Selecting UI elements based on an attribute, such as data-cy, is even described in the Cypress best practices section here.
There is also an example on how to create a custom command to have a common way for selecting the elements here.
The examples look like:
// cypress/support/commands.ts
Cypress.Commands.add('getBySel', (selector, ...args) => {
return cy.get(`[data-cy=${selector}]`, ...args)
})
Cypress.Commands.add('getBySelLike', (selector, ...args) => {
return cy.get(`[data-cy*=${selector}]`, ...args)
})
The first command looks for an exact data-cy attribute match. The second one looks for elements containing a data-cy like the provided argument.
If you add Cypress Testing Library to your project you get a bunch of commands around the data-testid attribute
ByTestId - find by data-testid attribute
getByTestId
queryByTestId
getAllByTestId
queryAllByTestId
findByTestId
findAllByTestId
See Cheatsheet for differences.
If you are tired of typing cy.get('[data-cy=name]'), then this profusion of selection methods may vex you even more.
The interesting part is the discussion about what selection methods are best for testing.
See Priority
Based on the Guiding Principles, your test should resemble how users interact with your code (component, page, etc.) as much as possible.
Interestingly, they place *ByTestId at the bottom of the list
The user cannot see (or hear) these, so this is only recommended for cases where you can't match by role or text or it doesn't make sense (e.g. the text is dynamic).

Cypress - Test belong to relationship case

Is it possible to use cypress for testing relationship between models?
For example, I have this kind of relationship: a teacher has many students, each student belongs to a teacher. After the teacher A logged in, at url "/my-students", he or she will see a list of all his or her students.
What I want to test is to make sure none of the students listed on "/my-students" page belong to teacher B than teacher A.
Can I test this case with cypress? Is it possible and how to do it if it's possible?
The short answer is "yes" you can absolutely do this kind of testing. There are dozens of ways, but I'm going to suggest what I consider to be the simplest approach.
Make sure the data being used by your website doesn't change. You want your tests to be deterministic... none of this run a database query to determine the expected results stuff.
The first time through, verify the page contents manually
Use cy.snapshot() to record the current page state for future comparison. This is an additional npm package from Gleb Bahmutov (a Cypress developer). Full instructions, including installation, can be found here.
Your hypothetical test would look something like this:
describe('student directory page', () => {
beforeEach(() => {
// Log in
cy.logIn('lizstewart#example.com') // This is usually a custom command; up to you
})
it('displays the correct students', () => {
// Go to the page
cy.visit('my-students')
// Check for the correct students
cy.get('#studentList').snapshot()
})
})
The first time the test runs it will pass no matter what, and will write out a file titled snapshots.js, which you can commit to your repo. All subsequent test runs will fail if the HTML output doesn't match the previous content exactly.
It's a blunt approach, but it's quick and effective.

d3.queue is not giving an output

This is the page I am trying to make it dynamic by enabling cross-filtering.
So the thing is they are having multiple API.
For the top first two: TOTAL CASES & DAILY CASES
They are using this API and the third one in the top is based on this API.
The bottom three AGE, GENDER, and NATIONALITY are from this API.
In all the API one thing is common that is a date but there are some API in which some data are missing for few dates like there is a gap( Not available for some of the dates).
So I thought of combining all the JSON API in terms of dates and then allow cross filter because I believe I can enable cross-filtering between them. Correct me If I am wrong.
Like If I click on gender female since it gives info about total cases where the patient was female so only confirmed cases from the Total cases will change not the recovered, deaths as data is not available. SO I guess I should combine the top 3 charts together and gender, age and nationality charts, together. Then Dc js would be able to handle nicely filtering between each segments (cases related to landmark, cases related to person info).
Line 123:
var log = console.log;
var q = queue()
.defer(d3.json, "https://api.covid19india.org/data.json")
.defer(d3.json, "https://api.rootnet.in/covid19-in/unofficial/covid19india.org/statewise/history");
q.await(function(error, data1, data2) {
log("==========>");
log("data1:", error,data1);
log("data2:", data2);
});
This is not working because I can't see console.log() output.
https://blockbuilder.org/ninjakx/8c48ab6481311aa0452046d66c4d8701
So my questions are:
1) Why d3.queue is not working?
2) Suggestion whether combining all the datas together and allowing a filltering is a good idea or not as there is limited data. Should I go for cross filtering between the same api charts. So in this case I will have 2 segments (cases related to landmark, cases related to person info)..
Using DC js I want to make it more interactive and display more info.
d3.queue is obsolete
The answer to your first question is cut-and-dried: you don't need d3.queue, and it was deprecated and removed in d3#5.
As of d3#5, D3's data loading APIs use ES6 Promises instead of asynchronous callbacks, so you can use Promise.all([...]) instead of d3.queue. Apparently no way to make the new API emit errors when called in the old way, so it just fails silently. :-/
The new way to write your code is
Promise.all([
d3.json("https://api.covid19india.org/data.json"),
d3.json("https://api.rootnet.in/covid19-in/unofficial/covid19india.org/statewise/history")
]).then(([data1,data2]) => {
log("==========>");
log("data1:", data1);
log("data2:", data2);
})
.catch(error => log('error', error))
I find this much easier to read and understand. A nice side effect is that if you neglect to do error handling (like most people), you'll automatically get a clear message in the log.
Working fork of your block.
Combining multiple data sets
Your second question is pretty open-ended, maybe it would be better to bring that to the dc.js users group?
In general, it's difficult to cross-filter more than one data set. You would have more than one chart group that redraws together, and you'd have to manually add handlers on some chart to initiate, clear filters, and redraw the other chart group.
I haven't seen too many dashboards that do this. You'd have to make it clear to users what is going on.

Routing Conventions in Can.js

So I’m looking to make some routes within my super cool can.js application. Aiming for something like this…
#!claims ClaimsController - lists claims
#!claims/:id ClaimController - views a single claim
#!claims/new ClaimController - creates a new claim
#!claims/:id/pdf - do nothing, the ClaimController will handle it
#!admin AdminController - loads my Administrative panel with menu
#!admin/users - do nothing, the AdminController will handle it
#!admin/settings - do nothing, the AdminController will handle it
So how might we do this?
“claims route”: function() { load('ClaimsController'); },
“claims/:id route”: function() { load('ClaimController'); },
“admin”: function() { load(‘AdminController’); },
Cool beans, we’re off. So what if someone sends a link to someone like...
http://myapp#!claims/1/pdf
Nothing happens! Ok, well let’s add the route.
“claims/:id/pdf route”: function() { load('ClaimController'); },
Great. Now that link works. Here, the router’s job is only to load the controller. The controller will recognize that the pdf action is wanted, and show the correct view.
So pretend I’ve loaded up a claim claims/:id and I edit one or two things. Then I click the Print Preview button to view the PDF and change my route to claims/:id/pdf.
What should happen… the Claim Controller is watching the route and shows the pdf view.
What actually happens… the router sees the change, matches the claims/:id/pdf route we added, and reloads the Claim Controller, displaying a fresh version of the claim pulled from the server/cache, losing my changes.
To try and define the problem, I need the router to identify when the route changes, what controller the route belongs to, and if the controller is already loaded, ignore it. But this is hard!
claims //
claims/:id // different controllers!
claims/:id //
claims/:id/pdf // same controller!
We could just bind on the "controller" change. So defining routes like can.route(':controller') and binding on :controller.
{can.route} controller
// or
can.route.bind('controller', function() {...})
But clicking on a claim (changing from ClaimsController to ClaimController) won't trigger, as the first token claim is the same in both cases.
Is there a convention I can lean on? Should I be specifying every single route in the app and checking if the controller is loaded? Are my preferred route urls just not working?
The following is how I setup routing in complex CanJS applications. You can see an example of this here.
First, do not use can.Control routes. It's an anti-pattern and will be removed in 3.0 for something like the ideas in this issue.
Instead you setup a routing app module that imports and sets up modules by convention similar to this which is used here.
I will explain how to setup a routing app module in a moment. But first, it's important to understand how can.route is different from how you are probably used to thinking of routing. Its difference makes it difficult to understand at first, but once you get it; you'll hopefully see how powerful and perfect it is for client-side routing.
Instead of thinking of urls, think of can.route's data. What is in can.route.attr(). For example, your URLs seem to have data like:
page - the primary area someone is dealing with
subpage - an optional secondary area within the page
id - the id of a type
For example, admin/users might want can.route.attr() to return:
{page: "admin", subpage: "users"}
And, claims/5 might translate into:
{page: "claims", id: "5"}
When I start building an application, I only use urls that look like #!page=admin&subpage=users and ignore the pretty routing until later. I build an application around state first and foremost.
Once I have the mental picture of the can.route.attr() data that encapsulates my application's state, I build a routing app module that listens to changes in can.route and sets up the right controls or components. Yours might look like:
can.route.bind("change", throttle(function(){
if( can.route.attr("page") == "admin" ) {
load("AdminController")
} else if(can.route.attr("page") === "claims" && can.route.attr("id") {
load("ClaimController")
} else if ( ... ) {
...
} else {
// by convention, load a controller for whatever page is
load(can.capitalize(can.route.attr("page")+"Controller")
}
}) );
Finally, after setting all of that up, I make my pretty routes map to my expected can.route.attr() values:
can.route(":page"); // for #!claims, #!admin
can.route("claims/new", {page: "claims", subpage: "new"});
can.route("claims/:id", {page: "claims"});
can.route("admin/:subpage",{page: "admin"});
By doing it this way, you keep your routes independent of rest of the application. Everything simply listens to changes in can.route's attributes. All your routing rules are maintained in one place.

What is The Rails Way for requesting an alternate view of all records?

I have a rails app that has a list of Products, and therefore I have an index action on my ProductsController that allows me to see a list of them all.
I want to have another view of the products that presents them with a lot more information and in a different format -- what's The Rails Way for doing that?
I figure my main options are:
pass a parameter (products/index.html?other_view=true) and then have an if else block in ProductsController#index that renders a different view as required. That feels a bit messy.
pass a parameter (products/index.html?other_view=true) and then have an if else block in my view (index.html.haml) that renders different html as required. (I already know this is not the right choice.)
Implement a new action on my controller (e.g.: ProductsController#detailed_index) that has it's own view (detailed_index.html.haml). Is that no longer RESTful?
Is one of those preferable, or is there another option I haven't considered?
Thanks!
Another way of doing it would be via a custom format. This is commonly done to provide mobile specific versions of pages, but I don't see why the same idea couldn't be applied here.
Register :detailed as an alias of text/html and then have index.detailed.haml (or .erb) with the extra information. If you need to load extra data for the detailed view you can do so within the respond_to block.
Then visitors to /somecollection/index.detailed should see the detailed view. You can link to it with some_collection_path(:format=>'detailed')
I'm not sure whether this is 'bettrr' than the alternatives but there is a certain logic I think to saying that a detailed view is just an alternative representation of the data, which is what formats are for.
After doing some reading, I think that adding a new RESTful action (option #3 in my question) is the way to go. Details are here: http://guides.rubyonrails.org/routing.html#adding-more-restful-actions
I've updated my routes.rb like this:
resources :products do
get 'detailed', :on => :collection
end
And added a corresponding action to my ProductsController:
def detailed
# full_details is a scope that eager-loads all the associations
respond_with Product.full_details
end
And then of course added a detailed.html.haml view that shows the products in a the detailed way I wanted. I can link to this with detailed_products_path which generates the URL /products/detailed.
After implementing this I'm sure this was the right way to go. As the RoR guides say, if I was doing a lot of custom actions it probably means I should have another controller, but just one extra action like this is easy to implement, is DRY and works well. It feels like The Rails Way. :-)

Resources