Sunday, May 8, 2016

Do I have to build maven webapp project everytime i make changes into static files?

Leave a Comment

I am using JBOSS AS7.1, Eclipse Luna for development. My eclipse installation does have a plugin installed for maven.

I have created my webapp project using maven command-line.

In my current set up, I have to build my maven project using mvn clean install every time for all changes, even for the static files like HTML, CSS.

Then, I have to deploy the generated WAR file using JBOSS console running at http://localhost:9990/console.

I am quite sure that there must be another way to do this. Surely, it does take a hell lot of time.

Please guide me to the approaches I can adopt for faster development.

2 Answers

Answers 1

One option is jrebel. It's not free though.

If you are not bound to JBOSS, you could use spring boot. It also supports automatic restart (spring boot devtools)

Answers 2

You can replace the static file in your target folder and launch a build skipping the compile phase.

It will save you a lot of time, when only updating static files.

It is not a good practice, but should let you achieve your goal.

How to:

  • Use the maven-clean-plugin to remove the files to replace from the target folder (or they will not be overwritten);
  • Use the resources tag if your static files are not the only content of the resources folder you want to copy (or your static files are not in resources folder at all);
  • Use the maven-compiler-plugin to skip the compile phase.

Customize this profile (and use it with mvn clean install -P skip-compile):

<profile>     <id>skip-compile</id>     <build>         <resources> <!-- optional -->             <resource>                 <directory>src/main/resources/META-INF</directory>                 <targetPath>META-INF</targetPath>                 <excludes>                     <exclude>**/*.xml</exclude>                 </excludes>                 <includes>                     <include>**/*.html</include>                     <include>**/*.css</include>                 </includes>             </resource>         </resources>         <plugins>             <plugin>                 <artifactId>maven-clean-plugin</artifactId>                 <version>3.0.0</version>                 <configuration>                 <excludeDefaultDirectories>true</excludeDefaultDirectories>                     <filesets>                         <fileset>                             <directory>${project.build.outputDirectory}/META-INF</directory>                             <excludes>                                 <exclude>**/not_to_delete.xml</exclude>                             </excludes>                             <includes>                                 <include>**/*.html</include>                                 <include>**/*.css</include>                             </includes>                             <followSymlinks>false</followSymlinks>                         </fileset>                     </filesets>                 </configuration>             </plugin>             <plugin>                 <groupId>org.apache.maven.plugins</groupId>                 <artifactId>maven-compiler-plugin</artifactId>                 <executions>                     <execution>                         <id>default-compile</id>                         <phase>compile</phase>                         <goals>                             <goal>compile</goal>                         </goals>                         <configuration>                             <skipMain>true</skipMain>                         </configuration>                     </execution>                 </executions>             </plugin>         </plugins>     </build> </profile> 
Read More

How to create a deployment package with SQL scripts in IIS

Leave a Comment

Using IIS-10, Web Deploy v3.6, Windows 10.

It is an ASP.NET Website (not application) using SQL Server 2014 for the back-end.
I was able to successfully create and deploy the package, but without SQL scripts. Could not find any info on how to do it for including SQL scripts in the package. Here is what I have tried:

  1. In IIS, under 'Default Web Site', select my web application.
  2. On the right hand side, click on 'Export Application'.
  3. Click on 'Manage Components'. Choose 'dbFullSql' as Provider name. For Path, enter connection string.
    eg: Data Source=xxx.xxx.xx.xxx;Initial Catalog=MyDB;Persist Security Info=True;User ID=testuser;Password=test@345;

When clicked on OK, nothing happens. The processing icon keeps showing. It had to be closed using task manager. enter image description here Also tried with the path of the .sql file, instead of connectionString. But no idea about what parameters to be entered& how /where to mention the connection string.

I would appreciate if anyone could guide me with the right procedure.

0 Answers

Read More

Scala higher kinded types in implicit def fails with “could not find implicit value”

Leave a Comment

I'm using implicit def to build a recursive HList type, to match several kind of higher kinded types of HList. I'm heavily inspired by this post.

This code is working perfectly :

sealed trait HList {   type Plus[L <: HList] <: HList }  class HNil extends HList {   type Plus[L <: HList] = L    def ::[T](v: T) = HCons(v, this) }  case class Appender[L1 <: HList, L2 <: HList, R <: HList](fn: (L1, L2) => R) {   def apply(l1: L1, l2: L2) = fn(l1, l2) }  object HNil extends HNil  object HList {   def ++[L1 <: HList, L2 <: HList](l1: L1, l2: L2)(implicit f: Appender[L1, L2, L1#Plus[L2]]): L1#Plus[L2] = f(l1, l2)    implicit def nilAppender[L <: HList]: Appender[HNil, L, L] = Appender((v: HNil, l: L) => l)    implicit def consAppender[T, L1 <: HList, L2 <: HList, R <: HList](implicit f: Appender[L1, L2, R]): Appender[HCons[T, L1], L2, HCons[T, R]] = {     Appender[HCons[T, L1], L2, HCons[T, R]]((l1: HCons[T, L1], l2: L2) => HCons(l1.head, f(l1.tail, l2)))   } }  case class HCons[T, U <: HList](head: T, tail: U) extends HList {   type Plus[L <: HList] = HCons[T, U#Plus[L]]    def ::[V](v: V) = HCons(v, this) }  import HList._  val hlist1 = 2.0 :: "hi" :: HNil val hlist2 = 1 :: HNil  val sum = ++(hlist1, hlist2) println("last element : " : + sum.tail.tail.head) // prints last element : 1" 

Now, I don't know why but if I try to add a ++ method on HCons, which simply calls existing HList.++ method, this is NOT working :

 case class HCons[T, U <: HList](head: T, tail: U) extends HList {  type Plus[L <: HList] = HCons[T, U#Plus[L]]    def ::[V](v: V) = HCons(v, this)    def ++[L2 <: HList](l2: L2) = HList.++(this,l2) } 

I get this compilation error:

could not find implicit value for parameter f: Appender[HCons[T,U],L2,HCons[T,U]#Plus[L2]] 

As HCons is a subtype of HList, like the L1 type defined by HList.++, I was thinking it was OK.

I've tried this but that's not working better :

implicit def consAppender[T, L1 <: HList, L2 <: HList, L3, R <: HList](implicit f: Appender[L1, L2, R], ev: L3 <:< HCons[T, L1]): Appender[HCons[T, L1], L2, HCons[T, R]] = {     Appender[HCons[T, L1], L2, HCons[T, R]]((l1: L3, l2: L2) => HCons(l1.head, f(l1.tail, l2)))   } 

What did I miss?

Thanks :)

1 Answers

Answers 1

You should change your ++ method definition from this:

 def ++[L2 <: HList](l2: L2) = HList.++(this,l2) 

to this:

def ++[L2 <: HList](l2: L2)(implicit f: Appender[HCons[T,U], L2, Plus[L2]]) = HList.++(this,l2) 

The compiler doesn't have enough information to select the right implicit value inside the method definition, but when you pass the appender from the outside, this example should pass:

val hlist1 = 2.0 :: "hi" :: HNil val hlist2 = 1 :: HNil println(hlist1++hlist2) 

Update 1: In the ++ method on HCons, we call the HList.++ method which requires an implicit parameter. This parameter must be of type Appender[HCons[T, U], L2, HCons[T, U#Plus[L2]]]. The compiler could fill this implicit parameter from HList.consAppender, but this in turn requires another implicit parameter of type Appender[U, L2, U#Plus[L2]]. This is the parameter that the compiler cannot discover itself. Knowing this, the code above can be simplified to:

def ++[L2 <: HList](l2: L2)(implicit f: Appender[U, L2, U#Plus[L2]]): Plus[L2] = HList.++(this, l2) 

Update 2: The compiler must fill in implicit parameters at the call site, in our case inside HCons.++ method (can be verified, e.g., with scalac -Xprint:typer). It can choose from implicits providing two appender types:

Appender[HNil, L, L] Appender[HCons[T, L1], L2, HCons[T, R]] 

The first one can be used only if type parameter U is HNil, the other only when U is HCons. But this information is not available inside HCons.++. It only knows that U <: HList but doesn't know which implementation of HList it is and therefore fails.

Read More

Invalid column deleted while trying to query a column

Leave a Comment

I am trying to execute the following query in my android app:

private static final String[] PROJECTION =             {                     Data.CONTACT_ID, Data.MIMETYPE,                     Data.DISPLAY_NAME, Phone.NUMBER,                     Phone.TYPE, StructuredName.GIVEN_NAME,                     StructuredName.MIDDLE_NAME,                     StructuredName.FAMILY_NAME,                     Data.DELETED             };   private static final String SELECTION = "(" + Data.MIMETYPE + " = ? AND "         + Phone.TYPE + " IN (?,?,?)) OR ("         + Data.MIMETYPE + " IN (?))";   private static final String[] SELECTION_ARGS =         {                 Phone.CONTENT_ITEM_TYPE,                 String.valueOf(Phone.TYPE_MOBILE),                 String.valueOf(Phone.TYPE_HOME),                 String.valueOf(Phone.TYPE_WORK),                 StructuredName.CONTENT_ITEM_TYPE         }; final Cursor cursor = mContext.getContentResolver().query(                 Data.CONTENT_URI, PROJECTION, SELECTION, SELECTION_ARGS, Data.CONTACT_ID); 

But I get the following error:

04-29 11:00:58.715  8588 15565 E DatabaseUtils: Writing exception to parcel 04-29 11:00:58.715  8588 15565 E  DatabaseUtils: java.lang.IllegalArgumentException: Invalid column deleted 04-29 11:00:58.715  8588 15565 E  DatabaseUtils:  at android.database.sqlite.SQLiteQueryBuilder.computeProjection(SQLiteQueryBuilder.java:632) 04-29 11:00:58.715  8588 15565 E  DatabaseUtils:  at android.database.sqlite.SQLiteQueryBuilder.buildQuery(SQLiteQueryBuilder.java:447) 04-29 11:00:58.715  8588 15565 E  DatabaseUtils:  at android.database.sqlite.SQLiteQueryBuilder.query(SQLiteQueryBuilder.java:387) 04-29 11:00:58.715  8588 15565 E  DatabaseUtils:  at com.android.providers.contacts.ContactsProvider2.doQuery(ContactsProvider2.java:6528) 04-29 11:00:58.715  8588 15565 E  DatabaseUtils:  at com.android.providers.contacts.ContactsProvider2.queryLocal(ContactsProvider2.java:6479) 04-29 11:00:58.715  8588 15565 E  DatabaseUtils:  at com.android.providers.contacts.ContactsProvider2.query(ContactsProvider2.java:5038) 04-29 11:00:58.715  8588 15565 E  DatabaseUtils:  at android.content.ContentProvider$Transport.query(ContentProvider.java:238) 04-29 11:00:58.715  8588 15565 E  DatabaseUtils:  at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:112) 04-29 11:00:58.715  8588 15565 E  DatabaseUtils:  at android.os.Binder.execTransact(Binder.java:453) 

I want to know if the contact that I am reading is deleted or not, so the Data.DELETED column is needed for me and that seems to be causing the error. But when I checked the Android documentation, DELETED does seem to be a valid column to use with ContactsContract.Data.

0 Answers

Read More

How to retrieve an image sent from a desktop app to a Rails API

Leave a Comment

I'm building a desktop app using Electron, which is basically JavaScript.

In it, I'm sending an image to my Rails API like this:

var myHeaders = new Headers(); myHeaders.append('Authorization', 'Token token=redacted'); myHeaders.append('Content-Type', 'application/json'); myHeaders.append('Accept', 'application/json');  ...  var formData = new FormData(); formData.append("img", base64EncodedImage); var myPost = {method: 'POST', headers: myHeaders, body: formData} fetch("url", myPost) 

(simplified)

In my Rails console, I see:

{"REMOTE_ADDR"=>"127.0.0.1", "REQUEST_METHOD"=>"POST", "REQUEST_PATH"=>"/task/screenshot", "PATH_INFO"=>"/task/screenshot", "REQUEST_URI"=>"/task/screenshot", "SERVER_PROTOCOL"=>"HTTP/1.1", "HTTP_VERSION"=>"HTTP/1.1", "HTTP_HOST"=>"localhost:3000", "HTTP_CONNECTION"=>"keep-alive", "CONTENT_LENGTH"=>"454856", "HTTP_ACCEPT"=>"application/json", "HTTP_ORIGIN"=>"null", "HTTP_USER_AGENT"=>"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) testivate-research-gigs/1.0.0 Chrome/49.0.2623.75 Electron/0.37.5 Safari/537.36", "HTTP_AUTHORIZATION"=>"Token token=redacted", "CONTENT_TYPE"=>"application/json", "HTTP_ACCEPT_ENCODING"=>"gzip, deflate", "HTTP_ACCEPT_LANGUAGE"=>"en-US", "rack.url_scheme"=>"http", "SERVER_NAME"=>"localhost", "SERVER_PORT"=>"3000", "QUERY_STRING"=>"", "rack.tempfiles"=>[#<Unicorn::TmpIO:/var/folders/k9/vnpft_6d7qs6xmdb9_4svvmw0000gn/T/0.19309304750270062>], "rack.input"=>#<Rack::Lint::InputWrapper:0x007fe1de317000 @input=#<Unicorn::TeeInput:0x007fe1de31d950 @len=454856, @chunked=false, @socket=#<Kgio::Socket:fd 7>, @parser=#<Unicorn::HttpParser:0x007fe1db08ce00>, @buf="", @rbuf="------WebKitFormBoundaryxkkhvE17qoI5ozlK\r\nContent-Disposition: form-data; name=\"img\"\r\n\r\ndata:image/jpeg;base64,iVBORw0KGgoAAAANSUhEU...

But I can't get to the image. This is what I see when I drop into the controller using Pry:

> request.body.read > request.body.rewind > request.body.read > request.env 

That is, I receive empty responses to most of the commands, and then it just hangs there indefinitely after I type request.env, without returning to the prompt.

How do I get to the image?

Thanks.

BTW, other actions that are receiving POSTs but not with embedded images are working perfectly. Previously, this action was working perfectly too when I was using XMLHttpRequest() not fetch(), but I've had to make the switch to turn my Google Chrome Extension into an Electron app.

UPDATE

I solved this problem for myself by uploading my images directly to S3 rather than to S3-via-Rails. It not only works but is faster than going via the app, which I now only have to tell where to look to find the image. But seeing as my original issue was not addressed, I'm leaving this question and the bounty open. Perhaps someone will solve it, claim the bounty, and write up an answer that someone else will find useful down the track.

2 Answers

Answers 1

If you are sending the image properly with POST and have it on the permitted params it should be available through regular parameters collection, then you can read your base64 encoded image and write it out:

File.open('some/path/to/image.jpg', 'wb') do|f|   f.write(Base64.decode64(base_64_encoded_data)) end 

Why are you reading from request.body directly anyway? params in your controller should have a hash of values from the form data.

Answers 2

I would test by sending a request from the desktop app to a locally served version of the API (i.e. run rails server from the terminal) and watch the rails console to see what is received. You should see a list of params. If the img param is included in the request then you are probably blacklisting via strong params in your rails controller

If you don't see the img param then you need to tweak the desktop app to send the form data correctly

Also, I suspect you should be using a post not a fetch from the desktop app

Read More

How do I write a range pipeline that uses temporary containers?

Leave a Comment

I have a third-party function with this signature:

std::vector<T> f(T t); 

I also have an existing potentially infinite range (of the range-v3 sort) of T named src. I want to create a pipeline that maps f to all elements of that range and flattens all the vectors into a single range with all their elements.

Instinctively, I would write the following.

 auto rng = src | view::transform(f) | view::join; 

However, this won't work, because we cannot create views of temporary containers.

How does range-v3 support such a range pipeline?

2 Answers

Answers 1

I suspect it just can't. None of the views have any machinery to store temporaries anywhere - that's explicitly against the concept of view from the docs:

A view is a lightweight wrapper that presents a view of an underlying sequence of elements in some custom way without mutating or copying it. Views are cheap to create and copy, and have non-owning reference semantics.

So in order for that join to work and outlive the expression, something somewhere has to hold onto those temporaries. That something could be an action. This would work (demo):

auto rng = src | view::transform(f) | action::join; 

except obviously not for src being infinite, and even for finite src probably adds too much overhead for you to want to use anyway.

You would probably have to copy/rewrite view::join to instead use some subtly modified version of view::all (required here) that instead of requiring an lvalue container (and returning an iterator pair into it), allowed for an rvalue container that it would store internally (and returning an iterator pair into that stored version). But that's several hundred lines' worth of copying code, so seems pretty unsatisfactory, even if that works.

Answers 2

Edited

Apparently, the code below violates the rule that views cannot own data they refer to. (However, I don't know if it's strictly forbidden to write something like this.)

I use ranges::view_facade to create a custom view. It holds a vector returned by f (one at a time), changing it to a range. This makes it possible to use view::join on a range of such ranges. Certainly, we can't have a random or bidirectional access to elements (but view::join itself degrades a range to an Input range), nor can we assign to them.

I copied struct MyRange from Eric Niebler's repository modifying it slightly.

#include <iostream> #include <range/v3/all.hpp>  using namespace ranges;  std::vector<int> f(int i) {     return std::vector<int>(static_cast<size_t>(i), i); }  template<typename T> struct MyRange: ranges::view_facade<MyRange<T>> { private:     friend struct ranges::range_access;     std::vector<T> data;     struct cursor {     private:         typename std::vector<T>::const_iterator iter;     public:         cursor() = default;         cursor(typename std::vector<T>::const_iterator it) : iter(it) {}         T const & get() const { return *iter; }         bool equal(cursor const &that) const { return iter == that.iter; }         void next() { ++iter; }         // Don't need those for an InputRange:         // void prev() { --iter; }         // std::ptrdiff_t distance_to(cursor const &that) const { return that.iter - iter; }         // void advance(std::ptrdiff_t n) { iter += n; }     };     cursor begin_cursor() const { return {data.begin()}; }     cursor   end_cursor() const { return {data.end()}; } public:     MyRange() = default;     explicit MyRange(const std::vector<T>& v) : data(v) {}     explicit MyRange(std::vector<T>&& v) noexcept : data (std::move(v)) {} };  template <typename T> MyRange<T> to_MyRange(std::vector<T> && v) {     return MyRange<T>(std::forward<std::vector<T>>(v)); }   int main() {     auto src = view::ints(1);        // infinite list      auto rng = src | view::transform(f) | view::transform(to_MyRange<int>) | view::join;      for_each(rng | view::take(42), [](int i) {         std::cout << i << ' ';     }); }  // Output: // 1 2 2 3 3 3 4 4 4 4 5 5 5 5 5 6 6 6 6 6 6 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 9 9 9 9 9 9  

Compiled with gcc 5.3.0.

Read More

HTML css Table misaligned Columns

Leave a Comment

I tried to create a table in AngularJS with sticky header and footer. I've managed to do that; here's a Plunker demo and code:

<body ng-controller="MainCtrl as ctrl">     <table class="table table-bordered table-hover">         <thead>             <tr>                 <th>                     Column1                 </th>                 <th>                     Column2                 </th>                 <th>                     Column3                 </th>                 <th>                     Column4                 </th>                 <th>                     Column5                 </th>             </tr>         </thead>         <tbody>             <tr class="info" ng-repeat="item in items">               <td>                 {{item.name}}               </td>               <td>                   {{item.type}}               </td>               <td>                   {{item.value}}               </td>               <td>                   {{item.code}}               </td>               <td>                   {{item.id}}               </td>             </tr>         </tbody>         <tfoot>             <tr>                 <th>                     Column1                 </th>                 <th>                     Column2                 </th>                 <th>                     Column3                 </th>                 <th>                     Column4                 </th>                 <th>                     Column5                 </th>             </tr>         </tfoot>     </table>   </body> 

But the only problems are:

  1. The column width isn't dynamic, as you can see, in the first row the data overflows into the 2nd column.
  2. The columns are misaligned.

Any idea how to fix this?

12 Answers

Answers 1

The criteria for success of this question are:

  • Pure CSS.
  • Dynamically sized columns.
  • Sticky header and footer that size with the columns.

What you want is impossible.

The reason why the cells are almost right is because the table is semi-there, but there are actually multiple tables in the document. By overriding the <table> elements display type to flex, you've rendered the page in several different groups. The <thead> and <tfoot> are their own table, and their <tbody> is its own table. They do not size to one another, rather to other table cells in their own group.

Other CSS guides about this topic require a fixed width. http://joshondesign.com/2015/05/23/csstable

After playing around with it (specifically, trying to move the thead and tfoot to fixed positions), I've decided that any attempt to give specific rules to the header/footer breaks the table layout and will cause the sizing to work differently, which is why fixed width is required on the cells. Even in the examples, they are broken in this way, but the fixed width nullifies the problem.

Your absolute easiest fix is to fix the widths and give them overflow-x properties.

The alternative is to use JavaScript. With JS, all bets are off, and you can do anything you imagine. Specifically, you could use JS to autosize cells. I remember having success with a jQuery plugin that accomplished that.
https://www.datatables.net/

Otherwise, no. I cannot find any example online that does what you need it to do. Any attempts to get this specific layout working is a hack and you're better off making a no-JS version with overflow-x cells and fixed widths, and a JS-enabled version that autosizes cells.

Answers 2

As you already know from the other answers, you should remove white-space: nowrap;, but there's also something you can do about the scrollbar:

table thead, table tfoot {     width: calc(100% - 17px); } 

enter image description here

Updated Plunker

this looks perfect on my PC because the Windows scrollbars are 17px broad. If you want to be safe and check the scrollbar width, use this:

window.onload = function() {     var tr = document.getElementById("__tbody").children[0];     var scrWidth = window.innerWidth - tr.clientWidth - 3;     var width = "calc(100% - " + scrWidth + "px)";     document.getElementById("__thead").style.width = width;     document.getElementById("__tfoot").style.width = width; } 

This calculates how broad the scrollbars are and then adjusts thead and tfoot. Of course then you have to set the ids <thead id="__thead">, <tbody id="__tbody"> and <tfoot id="__tfoot">.

Answers 3

In order to return the table back to its normal, dynamically-resizing self, there are a few steps to follow. Each step mentioned will give freedom back to its respective table elements, making their lives much simpler.

First, remove all instances of flex. You want the table to act like a table, right? Next, let your thead, tr, and tfoot be themselves as well. Why make them display as a table? Lastly, your tbody is being set to display as a block. This, in a sense, segregates it from its other table friends, namely thead and tfoot. This creates a lonely situation for everyone involved.

Your final code will look like this:

table {     table-layout: auto;     max-height: 300px; }  table thead, table tfoot, table tbody tr {     table-layout: fixed; }  tbody td, thead th, tfoot td{     border-right: 1px solid transparent;     vertical-align: middle;     white-space: nowrap; }  table thead {     width: 100%; } table tfoot {     width: 100%; } table tbody {     overflow-y: auto; }  table tbody tr {     width: 100%; } 

This will allow your table cells to be themselves--dynamically resizing as they see fit.

Answers 4

Remove

white-space: nowrap; 

and add

word-wrap: break-word; 

tfoot td{         border-right: 1px solid transparent;         vertical-align: middle;         word-wrap: break-word;     } 

Answers 5

Answer of first question:

td {     text-overflow: ellipsis !important;     overflow: hidden !important; } 

text-overflow: ellipsis is very useful because the user can copy the whole value.

Answer of second question:

Have a look at the answer of this question: Table scroll with HTML and CSS

It seems you need either javascript or inner table solution.

UPDATED for second answer:

Use following styles on your table and tbody (working on Chrome):

table {      overflow-y: visible !important; } tbody {     overflow-y: overlay !important; } 

Plunker Demo

Answers 6

1st answer: add the below css to td element

  td{       white-space: normal;       word-wrap: break-word;        } 

2nd answer: you need to create seperate table for header and footer and assign 20 % width to each td and th. It should work.

Answers 7

Here is your answer: https://plnkr.co/edit/cyN0qDuxIs8LjkxIhur5?p=preview

tbody td, thead th, tfoot td{     word-wrap:break-word;     table-layout:fixed;     white-space:normal; } 

Answers 8

Use bootstrap classes for the purpose of styling. Your custom styling(style.css) is creating the problem. Also, the <table> tag provides dynamic width of columns by default. An excerpt from How to create a dynamic width column in Twitter Bootstrap answer:

<table class="table"> <tr><th>Id</th>     <th>Name</th>      <th>Email address</th></tr> <tr><td>100001</td> <td>Joe</td>       <td>MamiePVillalobos@teleworm.us</td></tr> <tr><td>100</td>    <td>Christine</td> <td>ChristineJWilliams@dayrep.com</td></tr> <tr><td>1001</td>   <td>John</td>      <td>JohnLMiley@dayrep.com</td></tr> 

This will give you the desired output

Answers 9

https://plnkr.co/edit/3hYms9hRqzF2DV9yTBCG?p=preview

Added this in your css:

tr.info td {word-wrap: break-word; white-space: normal;} 

Answers 10

Your misalignment is coming because of followings

  1. overflowing text
  2. width of scrollbar in tbody

solution:

  1. give overflow-x:auto to 'td' and and some max-width/width
  2. make your last th as much extra bigger then others as your scroll-bar width is.

  3. for better look to scrollbars put custom css for scroll-bar

Add following css to your page and enjoy plunkerLink

th,td {       width: 20%!important;       overflow-x: auto;       padding: 10px;     }      th:last-of-type {       width: calc(20% + 6px)!important;     }      ::-webkit-scrollbar {       width: 3px!important;       height: 6px!important;     }      ::-webkit-scrollbar-button {       width: 0;       height: 0;     }      ::-webkit-scrollbar-thumb {       border: 2px solid #ccc;       background: #ccc;       border-radius: 8px;     }      ::-webkit-scrollbar-track-piece {       width: 3px!important;       border: 1px solid #fff;     } 

img

Answers 11

Here is more simpler solution. Take a look to this codepan.

Click linkhere

Answers 12

I have simply altered your style.css in Plunker demo as follows

/* Put your css in here */  table {     table-layout: auto;     display: flex;     flex-flow: column;     max-height: 300px; }  table thead, table tfoot, table tbody tr {     display: table;     table-layout: fixed; }  tbody td, thead th, tfoot td{     border-right: 1px solid transparent;     vertical-align: middle;     text-align: center;     white-space: normal;     word-wrap: break-word;     text-wrap: normal; }  table thead {     /*flex: 0 0 auto;*/     width: 100%; } table tfoot {     /*flex: 0 0 auto;*/     width: 100%; } table tbody {     flex: 1 1 auto;     display: block;     overflow-y: auto; }  table tbody tr {     width: 100%; } 
Read More

Scrolling is not working with CoordinatorLayout + parallax image + BottomSheetLayout

Leave a Comment

Introduction

I have an activity, which implements a common pattern with parallax header image and scrolling content using CoordinatorLayout, AppBarLayout and CollapsingToolbarLayout. My xml layout looks like this:

<android.support.design.widget.CoordinatorLayout     android:fitsSystemWindows="true"     android:layout_height="match_parent"     android:layout_width="match_parent">      <android.support.design.widget.AppBarLayout         android:fitsSystemWindows="true"         android:id="@+id/appbar"         android:layout_height="wrap_content"         android:layout_width="match_parent"         android:theme="@style/AppTheme.AppBarOverlay">          <android.support.design.widget.CollapsingToolbarLayout             android:fitsSystemWindows="true"             android:layout_height="wrap_content"             android:layout_width="match_parent"             app:contentScrim="?attr/colorPrimary"             app:layout_scrollFlags="scroll|exitUntilCollapsed">              <TextView                 android:background="@color/colorAccent"                 android:gravity="center"                 android:layout_height="250dp"                 android:layout_width="match_parent"                 android:text="ParallaxImage"                 app:layout_collapseMode="parallax"/>              <android.support.v7.widget.Toolbar                 android:id="@+id/toolbar"                 android:layout_height="?attr/actionBarSize"                 android:layout_width="match_parent"                 app:layout_collapseMode="pin"                 app:popupTheme="@style/AppTheme.PopupOverlay"/>          </android.support.design.widget.CollapsingToolbarLayout>     </android.support.design.widget.AppBarLayout>      <android.support.v4.widget.NestedScrollView         android:id="@+id/content"         android:layout_height="match_parent"         android:layout_width="match_parent"         app:layout_behavior="@string/appbar_scrolling_view_behavior">          <TextView             android:layout_height="wrap_content"             android:layout_margin="@dimen/text_margin"             android:layout_width="wrap_content"             android:text="@string/large_text"/>     </android.support.v4.widget.NestedScrollView> </android.support.design.widget.CoordinatorLayout> 

As you can see in the gif animation below, everything works correctly. You can scroll the whole screen from the content NestedScrollView as well as from the Toolbar or the parallax View.

AppBarLayout + NestedScrollView

Problem

Google introduced a BottomSheetBehavior class (Android design support library 23.2) to help developers to implement Bottom sheets. My xml layout with Bottom sheet looks like this:

<android.support.design.widget.CoordinatorLayout     xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:app="http://schemas.android.com/apk/res-auto"     android:layout_width="match_parent"     android:layout_height="match_parent"     android:fitsSystemWindows="true">      <android.support.design.widget.AppBarLayout         android:id="@+id/appbar"         android:layout_width="match_parent"         android:layout_height="wrap_content"         android:fitsSystemWindows="true"         android:theme="@style/AppTheme.AppBarOverlay">         <!-- ommited -->     </android.support.design.widget.AppBarLayout>      <android.support.v4.widget.NestedScrollView         android:id="@+id/content"         android:layout_width="match_parent"         android:layout_height="match_parent"         app:layout_behavior="@string/appbar_scrolling_view_behavior">         <!-- ommited -->     </android.support.v4.widget.NestedScrollView>      <LinearLayout         android:id="@+id/bottomSheet"         android:layout_width="match_parent"         android:layout_height="400dp"         android:background="@android:color/holo_blue_bright"         android:orientation="vertical"         app:behavior_peekHeight="?attr/actionBarSize"         app:layout_behavior="android.support.design.widget.BottomSheetBehavior">          <TextView             android:layout_width="match_parent"             android:layout_height="?attr/actionBarSize"             android:gravity="center_vertical"             android:paddingLeft="16dp"             android:paddingRight="16dp"             android:text="BottomSheetLayout"             android:textColor="@android:color/white"/>          <android.support.v4.widget.NestedScrollView             android:id="@+id/bottomSheetContent"             android:layout_width="match_parent"             android:layout_height="wrap_content"             android:scrollbars="vertical">              <TextView                 android:layout_width="match_parent"                 android:layout_height="400dp"                 android:background="@android:color/holo_green_dark"                 android:padding="16dp"                 android:text="@string/large_text"                 android:textColor="@android:color/white"/>          </android.support.v4.widget.NestedScrollView>     </LinearLayout>  </android.support.design.widget.CoordinatorLayout> 

And the result looks like this:

enter image description here

As you can see, Now I am not able to scroll, if I start to scroll from the parallax View. Scrolling from content NestedScrollView and from the Toolbar works as expected.

Question

How can I manage the scrolling to work from parallax View as well (the same way as in the first gif animation)? It seems that the BottomSheetBehavior intercepts touch events and prevents the AppBarLayout (AppBarLayoutBehavior) to handle the scroll. But the weird thing is that scrolling from Toolbar works and both parallax View and Toolbar are children of the AppBarLayout.

1 Answers

Answers 1

I think you should use NestedScrollView with the BottomSheetBehavior,replace blow as the bootemSheet!

<android.support.v4.widget.NestedScrollView     android:id="@+id/bottomSheet"     android:layout_width="match_parent"     android:layout_height="400dp"     android:background="@android:color/holo_blue_bright"     android:orientation="vertical"     app:behavior_peekHeight="?attr/actionBarSize"     app:layout_behavior="android.support.design.widget.BottomSheetBehavior">      <LinearLayout         android:layout_width="match_parent"         android:layout_height="wrap_content"         android:orientation="vertical">          <TextView             android:layout_width="match_parent"             android:layout_height="?attr/actionBarSize"             android:gravity="center_vertical"             android:paddingLeft="16dp"             android:paddingRight="16dp"             android:text="BottomSheetLayout"             android:textColor="@android:color/white"/>          <ScrollView             android:layout_width="match_parent"             android:layout_height="match_parent">              <TextView                 android:layout_width="match_parent"                 android:layout_height="400dp"                 android:background="@android:color/holo_green_dark"                 android:padding="16dp"                 android:text="@string/large_text"                 android:textColor="@android:color/white"/>         </ScrollView>     </LinearLayout>  </android.support.v4.widget.NestedScrollView> 

NestedScrollView can know how to nested with the toolbar,not the LinearLayout!

wish to help!!

Read More

Swift: UIBezierPath Stroke Animation from Center

Leave a Comment

I've been making a simple UIBezierPath animation with Swift. This path consists on creating a rounded rectangle with a colored border. The animation must be the drawing of the colored border. To do so, I've created a CAShapeLayer with a UIBezierPath(roundedRect:, cornerRadius: )

let layer = CAShapeLayer() var viewPrueba = UIView()  override func viewDidLoad() {     super.viewDidLoad()     // Do any additional setup after loading the view, typically from a nib.      viewPrueba = UIView(frame: CGRectMake(self.view.frame.width/2-100, self.view.frame.height/2 - 100, 200, 200))     self.view.addSubview(viewPrueba)     let path = UIBezierPath(roundedRect: CGRectMake(0, 0, 200, 200), cornerRadius: 40.0)     layer.path = path.CGPath     layer.fillColor = UIColor.clearColor().CGColor     layer.strokeColor = UIColor.blueColor().CGColor     layer.strokeStart = 0.0     layer.strokeEnd = 0.0     layer.lineWidth = 4.0     layer.lineJoin = kCALineJoinRound     viewPrueba.layer.addSublayer(layer)     let tapGR = UITapGestureRecognizer(target: self, action: #selector(ViewController.anim))     self.view.addGestureRecognizer(tapGR) }  func anim() {     let anim1 = CABasicAnimation(keyPath: "strokeEnd")     anim1.fromValue         = 0.0     anim1.toValue           = 1.0     anim1.duration          = 4.0     anim1.repeatCount       = 0     anim1.autoreverses      = false     anim1.removedOnCompletion = false     anim1.additive = true     anim1.fillMode = kCAFillModeForwards     self.layer.addAnimation(anim1, forKey: "strokeEnd") }` 

It works well. The only problem is that the animation starts from the top-left part of the square and not from the top-center. How can I do that?

The only thing I've found in order to achieve this is by doing it with a circle and not a rectangle, which is not what we want.

This is where the animation starts

Thanks

1 Answers

Answers 1

CoreAnimate animated as the same order as which the UIBezierPath was drawn.
The system method

+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius;      

return a UIBezierPath which was drawn from the top-left,so your animation started from the top-left.
But you can create your own UIBezierPath drawn form top-center:

func centerStartBezierPath(frame:CGRect,cornerRadius:CGFloat) -> UIBezierPath {     let path = UIBezierPath()     path.moveToPoint(CGPointMake(frame.width/2.0, 0))     path.addLineToPoint(CGPointMake(frame.width-cornerRadius, 0))     path.addArcWithCenter(CGPointMake(frame.width-cornerRadius, cornerRadius),                           radius: cornerRadius,                           startAngle: CGFloat(-M_PI/2),                           endAngle: 0,                           clockwise: true)     path.addLineToPoint(CGPointMake(frame.width, frame.height-cornerRadius))     path.addArcWithCenter(CGPointMake(frame.width-cornerRadius, frame.height-cornerRadius),                           radius: cornerRadius,                           startAngle: 0,                           endAngle: CGFloat(M_PI/2),                           clockwise: true)     path.addLineToPoint(CGPointMake(cornerRadius, frame.height))     path.addArcWithCenter(CGPointMake(cornerRadius, frame.height-cornerRadius),                           radius: cornerRadius,                           startAngle: CGFloat(M_PI/2),                           endAngle: CGFloat(M_PI),                           clockwise: true)     path.addLineToPoint(CGPointMake(0, cornerRadius))     path.addArcWithCenter(CGPointMake(cornerRadius, cornerRadius),                           radius: cornerRadius,                           startAngle: CGFloat(M_PI),                           endAngle: CGFloat(M_PI*3/2),                           clockwise: true)     path.closePath()      path.applyTransform(CGAffineTransformMakeTranslation(frame.origin.x, frame.origin.y))      return path; }     

And it works like this: top-center start animation
You can also change the code,and start from any point you want.

Read More

Saturday, May 7, 2016

Serve git-lfs files from express' public folder

Leave a Comment

I'm using node.js (express) on Heroku, where the slug size is limited to 300MB.

In order to keep my slug small, I'd like to use git-lfs to track my express' public folder.

In that way all my assets (images, videos...) are uploaded to a lfs-store (say AWS S3) and git-lfs leaves a pointer file (with probably the S3 URL in it).

I'd like express redirects to the remote S3 file when serving files from the public folder.

My problem is I don't kwon how to retrieve the URL from the pointer file's content...

app.use('/public/:pointerfile', function (req, res, next) {   var file = req.params.pointerfile;   fs.readFile('public/'+file, function (er, data) {     if (er) return next(er);      var url = retrieveUrl(data); // <-- HELP ME HERE with the retrieveUrl function      res.redirect(url);   }); }); 

Don't you think it will not be too expensive to make express read and parse potentially all the public/* files. Maybe I could cache the URL once parsed?

1 Answers

Answers 1

Actually the pointer file doesn't contain any url information in it (as can be seen in the link you provided, or here) - it just keeps the oid(Object ID) for the blob which is just its sha256.

You can however achieve what you're looking for using the oid and the lfs api that allows you to download specific oids using the batch request.

You can tell what is the endpoint that's used to store your blobs from .git/config which can accept non-default lfsurl tags such as:

[remote "origin"]    url = https://...    fetch = +refs/heads/*:refs/remotes/origin/*    lfsurl = "https://..." 

or a separate

[lfs]    url = "https://..." 

If there's no lfsurl tag then you're using GitHub's endpoint (which may in turn redirect to S3):

Git remote: https://git-server.com/user/repo.git Git LFS endpoint: https://git-server.com/user/repo.git/info/lfs  Git remote: git@git-server.com:user/repo.git Git LFS endpoint: https://git-server.com/user/repo.git/info/lfs 

But you should work against it and not S3 directly, as GitHub's redirect response will probably contain some authentication information as well.

Check the batch response doc to see the response structure - you will basically need to parse the relevant parts and make your own call to retrieve the blobs (which is what git lfs would've done in your stead during checkout).

A typical response (taken from the doc I referenced) would look something like:

{    "_links": {      "download": {        "href": "https://storage-server.com/OID",        "header": {          "Authorization": "Basic ...",        }      }   } } 

So you would GET https://storage-server.com/OID with whatever headers was returned from the batch response - the last step will be to rename the blob that was returned (it's name will typically be just the oid as git lfs uses checksum based storage) - the pointer file has the original resource's name so just rename the blob to that.

Read More

Proximity alert for locations saved at server

Leave a Comment

I did some search but could not find a suitable answer.

My App should compare with multiple locations for proximity. This means I will not be able to save all the locations into my app to confirm the proximity using locationManager. I want the proximity confirmation to be done in the server

What would be the best way to implement this?

Would it be sensible if the app asks for proximity confirmation every time the devices moves around?

4 Answers

Answers 1

I would try a different approach, since location updates from GPS are made once per second, and I don't think it's a good idea to ask the server each second for proximity if you have a large amount of devices.
Think of this idea -

  1. Get the device's initial location and send it to the server.
  2. Decide on a sensible radius that the device will stay within it for the next 5-10 minutes. Also make sure that you don't have "too many" points in that radius, or you can narrow the radius in that case. It's up to you to decide the radius and the number of points, depending on your usage, number of points etc.
  3. Send form the server all the locations within that radius to the device.
  4. Let the device calculate the proximity by itself.
  5. When the device moves out of the initial radius - update the server and get the new relevant locations. This can be done easily - call the radius r. Save the initial location of the device, and calculate the distance between the current and initail location. When it is "close enough" to r - update the server.

Answers 2

In your case, simply, you can send the received locations to your server and then make required calculations on server. But don't forget that you will be dealing with those questions

  • How many devices send location to server ?
  • How frequently each device send location to server ?

Also the responsibility of detecting a device has entered an area on the server

I think you can reduce the complexity of the all things by using geofencing api, link

  • No need to send each location to server.
  • Each device individually detects itself has entered or exited an area.

EDIT

Otherwise you will be doing entered/exited calculations on server for unlimited count of device, whenever each device's location has changed.

Before we were doing similar thing in my previous company, calculating enter/exit time and enter durations. but via real gps devices on buses

  • We have almost 100 points(geofence) in city. So you can think that those points are on a few routes

  • Each gps device on bus is sending location to server periodically.

  • When the bus has finished it's route, server reviews device's all received locations on the route.

  • Compares each geofence with each location of bus.

This is the real scenario. You can call it "server based geofencing".

Answers 3

You could do a simple k-d tree implementation on the server side to store the coordinates.

Send the coordinates of the device over, which can be determined at whatever interval you need. If it's every 5 seconds, or 10 seconds it doesn't really matter. This will mainly be decided by the minimum distance between each of the coordinates/radius. If they're closer, you may need to update it more frequently.

Using the k-d tree finding the nearest neighbor would be O(log(n)). However, you make a slight modification where you can start adding the nodes to a list as long as they are within the certain radius of your device coordinates. In fact if you store it locally as a k-d tree as well then you can pick the furthest nodes in O(log(n))

Now on the second update when the device location is sent again, you can quickly update it since you have the existing locations. Say you move in the x direction by 5. You can drop the points that are now outside of the radius at x - 5. The new proximity, you do the same nearest neighbor search, adding in nodes as they're within the radius, but this time starting with the cached nodes closest to the direction you are moving in.

Combining that with an interval tree for radiuses. So say 0 to 1, 1 to 2, 2 to 3, as your intervals. You can pick out everything within a certain radius in O(log(n)) time as well. Those should be pointers to nodes in the k-d tree. That would simplify the radius calculations and finding the location if you're willing to sacrifice some memory for efficiency.

Answers 4

For a "fast" way to implement it on the server side you can use the mondodb $near geospatial query.

https://docs.mongodb.org/manual/reference/operator/query/near/

While on the mobile side you can use the minDistance property for the location updates. You can set it to a reasonable distance 20m/50m depending on the average distance between your locations.

http://developer.android.com/reference/android/location/LocationManager.html#requestLocationUpdates(java.lang.String,%20long,%20float,%20android.location.LocationListener)

Read More

Displaying null values after first users login inside android

Leave a Comment

I have 3 fragments inside an app and in one of them I display users name from the SQLite database. What happens is when I register a new user and login first time with it, inside the textview where the users name suppose to appear, it displays NULL value, but when I logout and login again with the same user, name appears as it should.

After registering user, all the data is inserted inside a database, I have checked.

Any ideas what can cause this problem? I will add some code on request as I have no idea which part of the code, fragments or java files might cause this..

EDITED

I have added some code to help resolve this issue.

Login function inside the main screen (once app launches):

private void checkLogin(final String email, final String password) {     // Tag used to cancel the request     String tag_string_req = "req_login";      pDialog.setMessage("Logging in ...");     showDialog();      StringRequest strReq = new StringRequest(Request.Method.POST,             AppConfig.URL_LOGIN, new Response.Listener<String>() {          @Override         public void onResponse(String response) {              Log.d(TAG, "Login Response: " + response.toString());             hideDialog();               try {                 JSONObject jObj = new JSONObject(response);                 boolean error = jObj.getBoolean("error");                  // Check for error node in json                 if (!error) {                     // user successfully logged in                     // Create login session                     session.setLogin(true);                     // Now store the user in SQLite                     String uid = jObj.getString("uid");                      JSONObject user = jObj.getJSONObject("user");                     String name = user.getString("name");                     String email = user.getString("email");                     String created_at = user.getString("created_at");                      // Inserting row in users table                     db.addUser(name, email, uid, created_at);                      // Launch main activity                     Intent intent = new Intent(Main.this,                             Logged.class);                     startActivity(intent);                     finish();                 } else {                      error_msg.setVisibility(View.VISIBLE);                     String msg = jObj.getString("error_msg");                     error_msg.setText(msg);                 }             } catch (JSONException e) {                 // JSON error                 e.printStackTrace();                 Toast.makeText(getApplicationContext(), "Json error: " + e.getMessage(), Toast.LENGTH_LONG).show();             }          }     }, new Response.ErrorListener() {          @Override         public void onErrorResponse(VolleyError error) {             Log.e(TAG, "Login Error: " + error.getMessage());             Toast.makeText(getApplicationContext(),                     error.getMessage(), Toast.LENGTH_LONG).show();             hideDialog();         }     }) {          @Override         protected Map<String, String> getParams() {             // Posting parameters to login url             Map<String, String> params = new HashMap<String, String>();             params.put("email", email);             params.put("password", password);              return params;         }      };      // Adding request to request queue     AppController.getInstance().addToRequestQueue(strReq, tag_string_req); } 

onViewCreated func inside fragment where users name should be diplayed:

@Override public void onViewCreated(View view, Bundle savedInstanceState){      profileName = (TextView) getActivity().findViewById(R.id.profileName);       // SqLite database handler     db = new SQLiteHandler(getActivity().getApplicationContext());      // session manager     session = new SessionManager(getActivity().getApplicationContext());      if (!session.isLoggedIn()) {         logoutUser();     }      // Fetching user details from SQLite     HashMap<String, String> user = db.getUserDetails();      String name = user.get("name");      // Displaying the user details on the screen     profileName.setText(name);  } 

Register function part:

  public void onResponse(String response) {             Log.d(TAG, "Register Response: " + response.toString());             hideDialog();              try {                 JSONObject jObj = new JSONObject(response);                 boolean error = jObj.getBoolean("error");                 if (!error) {                      // User successfully stored in MySQL                     // Now store the user in sqlite                     String uid = jObj.getString("uid");                      JSONObject user = jObj.getJSONObject("user");                     String name = user.getString("name");                     String email = user.getString("email");                     String created_at = user.getString("created_at");                      // Inserting row in users table                     db.addUser(name, email, uid, created_at);                      // Launch login activity                     Intent intent = new Intent(                             Register.this,                             Main.class);                     startActivity(intent);                     finish();                 } else {                      // Error occurred in registration. Get the error                     // message                     error_msg.setVisibility(View.VISIBLE);                     String msg = jObj.getString("error_msg");                     error_msg.setText(msg);                   }             } catch (JSONException e) {                 e.printStackTrace();             }          }     }, new Response.ErrorListener() {          @Override         public void onErrorResponse(VolleyError error) {             Log.e(TAG, "Registration Error: " + error.getMessage());             Toast.makeText(getApplicationContext(),                     error.getMessage(), Toast.LENGTH_LONG).show();             hideDialog();         }     }) { 

1 Answers

Answers 1

I managed to sort this problem myself.. The problem was not in java files but in php files sending to java for some reason..

In php function I used:

$stmt->bind_result($id, $unique_id, $name, $email, $encrypted_password, $salt, $created_at, $updated_at); $user = $stmt->fetch(); 

But then changed to

$user = $stmt->get_result()->fetch_assoc();  

And that fixed the problem..

Read More

Open paradox / borland database as single file

Leave a Comment

my question is: how to connect java tp paradox / borland database ".DB" single files?

Here's what I have: screenshot So, it's Paradox 7 database files.

I'm trying drivers: http://www.hxtt.com/paradox.html & https://code.google.com/archive/p/paradoxdriver/ as:

String url = "jdbc:paradox:/D:/BABAK/powerGold/SongTitle.DB"; Connection con = DriverManager.getConnection(url); 

But both throws exceptions like:

D:/BABAK/powerGold/SongTitle.DB isn't a database directory path! 

As you can see, it is trying to find some database folder, but I have only single files! Also, "jdbc:paradox:/D:/BABAK/powerGold" (path to all .DB files folder) didn't work as well.

So, anybody, please help me to figure out, how to open this type of DB in my Java app.

1 Answers

Answers 1

you're not trying to open the database doing so but a specific file of the whole DB. In fact your DB is composed of files .db, .px ....

The best approach to do so, is to migrate since this DB is not supported, and realy brings a lot of bugs.

I will recommand you to use migrate your database.

  1. install Paradox Database Reader or Editor
  2. export tables to CSV files
  3. import tables in mysql Database (for example)

If you still want to connect this DB without migration with java, share in private a file .db and will give a try now.

Read More

Xcode 7.3 crashes when breakpoint set or app crashes

Leave a Comment

I am having this issue and when I searched it on Stack Overflow I saw that many people have had this before:

First of all, you can find the crash report here: http://pastebin.com/c726EUip

What I've tried so far:

  • I set the "Enable Clang Module Debugging" in Build Setting to NO
  • I did pod update
  • Tried to change LLDB to GDB but i think xcode no longer has this option

This is the list of frameworks:

List of Frameworks

Here are links to questions from people with the same issue:

I am totally desperate on this, as I cannot debug my work properly.

Anyone have ideas?

1 Answers

Answers 1

Try going through all these steps in the exact same order http://stackoverflow.com/a/28371711/821053 This solved my debugging problems a couple of times.

Read More

node certificate store, does node read only from hard coded list of certificates?

1 comment

After struggling for a while, is there any way to add new certificate to the list of certificates node trusts?

It seems that node will trust only to certificates stored in hard coded list of certificates: https://github.com/nodejs/node/blob/master/src/node_root_certs.h

So for example, node app should communicate with https://foo-bar-baz.com that use self signed certificate, causing request to that domain to return something like:
[RequestError: Error: certificate has expired]

Apparently how this can be fixed in java is adding https://foo-bar-baz.comcertificate to $JAVA_HOME/lib/security/cacerts.

Does node only read certificates from mentioned hard coded list? or it can read also from some OS certificate store? If just from hard coded list:

  1. Why? What can be the reason to implement it in that way?
  2. If some certificate gets forged, only what one can do is to wait next node version?
  3. If one want to add self-signed certificate, impossible?

(One could edit probably hard coded list to add/remove certificate, but i wouldn't feel comfortable with changing node source, also from similar question Where is node's certificate store? one could add certificate while doing request but it is not in scope of this question. Similar question is posted before 2 years, and from what i have investigated situation is the same today)

1 Answers

Answers 1

You are calling them hard coded "list of certificates" ... the list is Certificate Granting Authorities, not certificates ... current behaviour is intentional for good reasons ... it would be very bad if a web server (nodejs) rendered a Green Padlock for unvalidated toy self signed certs

I suggest you use a better technique to synthesize your certificates which will give you valid certs which enable that Green Padlock ...

Run through this tutorial to get valid certs (free) for your domain which are production ready ... also gr8 for kicking tyres : https://letsecure.me/secure-web-deployment-with-lets-encrypt-and-nginx/

Read More

Get params validation on viewsets.ModelViewSet

Leave a Comment

I am new to django and building a REST API using django-rest-framework. I have written some code to check whether the user has supplied some parameters or not.But that is very ugly with lot of if conditions, so i want to refactor it.Below is the code that i have written please suggest how to refactor it.

I am looking for some django based validations.

class AssetsViewSet(viewsets.ModelViewSet):     queryset = Assets.objects.using("gpr").all()   def create(self, request):     assets = []     farming_details = {}      bluenumberid = request.data.get('bluenumberid', None)     if not bluenumberid:         return Response({'error': 'BlueNumber is required.'})      actorid = request.data.get('actorid', None)     if not actorid:         return Response({'error': 'Actorid is required.'})      asset_details = request.data.get('asset_details', None)     if not asset_details:         return Response({'error': 'AssetDetails is required.'})      for asset_detail in asset_details:         location = asset_detail.get('location', None)       if not location:         return Response({'error': 'location details is required.'})        assettype = asset_detail.get('type', None)       if not assettype:         return Response({'error': 'assettype is required.'})        asset_relationship = asset_detail.get('asset_relationship', None)       if not asset_relationship:         return Response({'error': 'asset_relationship is required.'})        subdivision_code = location.get('subdivision_code', None)       if not subdivision_code:         return Response({'error': 'subdivision_code is required.'})        country_code = location.get('country_code', None)       if not country_code:         return Response({'error': 'country_code is required.'})        locationtype = location.get('locationtype', None)       if not locationtype:         return Response({'error': 'locationtype is required.'})        latitude = location.get('latitude', None)       if not latitude:         return Response({'error': 'latitude is required.'})        longitude = location.get('longitude', None)       if not longitude:         return Response({'error': 'longitude is required.'})        try:         country_instance = Countries.objects.using('gpr').get(countrycode=country_code)       except:         return Response({'error': 'Unable to find country with countrycode ' + str(country_code)})       try:         subdivision_instance = NationalSubdivisions.objects.using('gpr').get(subdivisioncode=subdivision_code, countrycode=country_code)       except:           return Response({'error': 'Unable to find subdivision with countrycode ' + str(country_code) + ' and' + ' subdivisioncode ' + str(subdivision_code)})        kwargs = {}       kwargs['pobox'] = location.get('pobox', '')       kwargs['sublocation'] = location.get('sublocation', '')       kwargs['streetaddressone'] = location.get('streetaddressone', '')       kwargs['streetaddresstwo'] = location.get('streetaddresstwo', '')       kwargs['streetaddressthree'] = location.get('streetaddressthree', '')       kwargs['city'] = location.get('city', '')       kwargs['postalcode'] = location.get('postalcode', '')        cursor = connections['gpr'].cursor()       cursor.execute("Select uuid() as uuid")       u = cursor.fetchall()       uuid = u[0][0].replace("-", "")        kwargs['locationid'] = uuid     #   l.refresh_from_db()       try:         Locations.objects.using('gpr').create_location(locationtype=locationtype, latitude=latitude, longitude=longitude, countrycode=country_instance, subdivisioncode = subdivision_instance, **kwargs)       except (TypeError, ValueError):          return Response({'error': 'Error while saving location'})        try:         location_entry = Locations.objects.using('gpr').get(locationid=uuid)       except:         return Response({'error': 'Unable to find location with locationid ' + str(uuid)})        asset_entry = Assets.objects.using('gpr').create(locationid=location_entry, assettype=assettype)       asset_entry = Assets.objects.using('gpr').filter(locationid=location_entry, assettype=assettype).latest('assetinserted')       farming_details[asset_entry.assetid] = []        try:         actor = Actors.objects.using('gpr').get(actorid = actorid)       except:         return Response({'error': 'Unable to find actor with actorid ' + str(actorid)})       assetrelationship = AssetRelationships.objects.using('gpr').create(assetid= asset_entry, actorid= actor,assetrelationship=asset_relationship)       assets.append(asset_entry)        if assettype=="Farm or pasture land":             hectares = asset_detail.get('hectares', None)             if hectares is None:               return Response({'error': 'hectares must be a decimal number'})             try:               farmingasset = FarmingAssets.objects.using('gpr').create(assetid=asset_entry, hectares=hectares)             except ValidationError:               return Response({'error': 'hectares must be decimal value.'})             farmingasset = FarmingAssets.objects.using('gpr').filter(assetid=asset_entry, hectares=hectares).last()             for type_detail in asset_detail.get('type_details', []):               crop = type_detail.get('crop', '')               hectare = type_detail.get('hectare', '')               if crop != '' and hectare != '':                 try:                   h3code = ProductCodes.objects.using('gpr').get(h3code=crop)                 except:                   return Response({'error': 'Unable to find ProductCode with h3code' + str(crop)})                 try:                   farming = Farming.objects.using('gpr').create(assetid=farmingasset, h3code=h3code, annualyield=hectare)                   farming_details[asset_entry.assetid].append(farming.farmingid)                 except Exception as e:                   return Response({'error': e})               else:                 return Response({'error': 'crop with hectare is required.'})     i = 0     data = {}     for asset in assets:         if farming_details[asset.assetid]:           data[i] = {"assetid": asset.assetid, "assetbluenumber": asset.assetuniversalid, "farming_ids": farming_details[asset.assetid]}         else:           data[i] = {"assetid": asset.assetid, "assetbluenumber": asset.assetuniversalid}         i+=1     return Response(data) 

Asset Model

class Assets(models.Model):     assetid = models.CharField(db_column='AssetID', primary_key=True, max_length=255)  # Field name made lowercase.     assetname = models.CharField(db_column='AssetName', max_length=255, blank=True, null=True)  # Field name made lowercase.     locationid = models.ForeignKey('Locations', models.DO_NOTHING, db_column='LocationID')  # Field name made lowercase.     assetuniversalid = models.CharField(db_column='AssetBluenumber', unique=True, blank=True, null=True, max_length=255)  # Field name made lowercase.     assettype = models.CharField(db_column='AssetType', max_length=45, blank=True, null=True)  # Field name made lowercase.     assetinserted = models.DateTimeField(db_column='AssetInserted', blank=True, null=True, auto_now_add=True)  # Field name made lowercase.     assetupdated = models.DateTimeField(db_column='AssetUpdated', blank=True, null=True, auto_now=True)  # Field name made lowercase. 

3 Answers

Answers 1

You can make serializers, they have a very easy way to validate your data. As in your case all the fields seem to be required it becomes even easier.

Create a file on you api app like:

serializers.py

#Import Serializers lib from rest_framework import serializers  #Import your models here (You can put more than one serializer in one file) from assets.model import Assets  #Now make you serializer class class AssetsSerializer(serializers.ModelSerializer):     class Meta:         model = Profile         fields = '__all__'          #This last line will put all the fields on you serializer         #but you can also especify only some fields like:         #fields = ('assetid', 'assetname') 

On you view you can use your serializer(s) class to validate you data.

views.py

#Serializers from assets.serializers import AssetsSerializer  #Libraries you can use from django.http import Http404 from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status  class AssetsViewSet(viewsets.ModelViewSet):     queryset = Assets.objects.using("gpr").all()      def create(self, request):         assets = []         farming_details = {}         #Set your serializer         serializer = AssetsSerializer(data=request.data)         if serializer.is_valid(): #MAGIC HAPPENS HERE             #... Here you do the routine you do when the data is valid             #You can use the serializer as an object of you Assets Model             #Save it             serializer.save()             return Response(serializer.data, status=status.HTTP_201_CREATED)         return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 

i took this all from the documentation. You can learn a lot doing the tutorial from the official site. I hope it helps.

Answers 2

You can do something like:

for param in ['bluenumberid', 'actorid', 'asset_details']:     if param not in request.data.keys():         raise Response({'error': '%s is required.' % param})   ...  for asset_detail in asset_details:     for param in ['location', ..., 'longitude']:         if param not in asset_detail.keys():             raise Response({'error': '%s is required.' % param})  

Answers 3

This is just a guide that you can follow for refactoring, of course many other things can be improved while performing this:

  • make a ModelSerializer for model Assets
  • AssetsModelSerializer should handle validation
  • within AssettsModelSerializer add any related ModelSerializer (like Locations) that has specific validation and representation
  • move the create method to AssetsModelSerializer and just handle there the model creation
  • AssetModelSerializer should provide a specific to_representation (if needed)
  • The AssetsViewSet is doing more then one thing as I see (especially the last part with FarmingAssets objects) can you split that logic in another view? or route?
Read More

Strange Jackson Illegal character ((CTRL-CHAR, code 0)) Exception in Map Reduce Combiner

Leave a Comment

I have a Map-Reduce job with a mapper which takes a record and converts it into an object, an instance of MyObject, which is marshalled to JSON using Jackson. The value is just another Text field in the record.

The relevant piece of the mapper is something like the following:

ObjectMapper mapper = new ObjectMapper(); MyObject val = new MyObject(); val.setA(stringA); val.setB(stringB); Writer strWriter = new StringWriter(); mapper.writeValue(strWriter, val); key.set(strWriter.toString()); 

The outputs of the mapper are sent to a Combiner which unmarshalls the JSON object and aggregates key-value pairs. It is conceptually very simple and is something like:

public void reduce(Text key, Iterable<IntWritable> values, Context cxt)      throws IOException, InterruptedException {     int count = 0;     TermIndex x = _mapper.readValue(key.toString(), MyObject.class);     for (IntWritable int : values) ++count;     ...     emit (key, value) } 

The MyObject class consists of two fields (both strings), get/set methods and a default constructor. One of the fields stores snippets of text based on a web crawl, but is always a string.

public class MyObject {   private String A;   private String B;    public MyObject() {}    public String getA() {     return A;   }   public void setA(String A) {     this.A = A;   }   public String getB() {     return B;   }    public void setIdx(String B) {     this.B = B;   } } 

My MapReduce job appears to be running fine until it reaches certain records, which I cannot easily access (because the mapper is generating the records from a crawl), and the following exception is being thrown:

Error: com.fasterxml.jackson.core.JsonParseException:       Illegal character ((CTRL-CHAR, code 0)): only regular white space (\r, \n, \t) is allowed between tokens      at [Source: java.io.StringReader@5ae2bee7; line: 1, column: 3] 

Would anyone have any suggestions about the cause of this?

1 Answers

Answers 1

Use a JSON library like Gson (add dependencies to Hadoop classpath) to escape invalid characters in your string.

Read More

Rails + Jasmine-Ajax: what is the correct way to test code triggered by `ajax:success` (jquery-ujs)

Leave a Comment

I am trying to test a certain internal library that has some JS behavior triggered on the ajax:success event.

The library creates a link that looks like this:

<%= link_to 'click here', '/some_path', class: 'special-link', remote: true %> 

And in the JS part of the library there is event binding code, which is the part I want to black-box test through its effect on the DOM:

$(document).on 'ajax:success', '.special-link', (e, data, status, xhr) ->   # Code that has some effect on the DOM as a function of the server response 

The library works as expected in the browser. However, when I try to test the library in Jasmine by calling $('.special-link').click(), the desirable effect on the DOM cannot be observed.

The issue, it seems, is that the ajax:success event does not get triggered:

describe 'my library', ->   beforeEach ->     MagicLamp.load('fixture') # Fixture library that injects the link above to the DOM     jasmine.Ajax.install()     jasmine.Ajax.stubRequest('/some_path').andReturn({       responseText: 'response that is supposed to trigger some effect on the DOM'})    afterEach ->     jasmine.Ajax.uninstall()    # Works. The fixtures are loading properly   it '[sanity] loads fixtures correctly', ->     expect($('.special-link').length).toEqual(1)    # Works. The jquery-ujs correctly triggers an ajax request on click   it '[sanity] triggers the ajax call', ->     $('.special-link').click()      expect(jasmine.Ajax.requests.mostRecent().url).toContain('/some_path')    # Works. Code that tests a click event-triggering seems to be supported by Jasmine   it '[sanity] knows how to handle click events', ->     spy = jasmine.createSpy('my spy')     $('.special-link').on 'click', spy     $('.special-link').click()     expect(spy).toHaveBeenCalled()    # Does not work. Same code from above on the desired `ajax:success` event does not work   it 'knows how to handle ajax:success events', ->     spy = jasmine.createSpy('my spy')     $('.special-link').on 'ajax:success', spy     $('.special-link').click()     expect(spy).toHaveBeenCalled() 

What is the right way to test the effect on the DOM of code that runs in ajax:success events?

2 Answers

Answers 1

Have you tried simply spying on the ajax function? For that, you need to use spyOn and force it to call the success event handler. That will let you test what you expect to happen when it's called.

it 'knows how to handle ajax:success events', ->   spyOn($, "ajax").and.callFake( (e) ->     e.success({});   )    $('.special-link').click()    # expect some method to be called or something to be changed in the DOM 

Answers 2

Here's how we would handle this sort of thing on my team.

it 'knows how to handle ajax:success events', ->   spyOn($.fn, 'on');   expect($.fn.on).toHaveBeenCalledWith('ajax:success',                                         '.special-link'                                        some_func); 

This pattern extends well to testing other 'on' events, too. Say we have some jQuery like this:

$document.on('myCustomEvent', '.some_selector', somecode.custom_func); $document.on('ajax:error', '.some_selector', somecode.failure_func); 

Then we can test it using this pattern:

beforeEach ->   spyOn($.fn, 'on');   somecode.init(); 

Testing an Ajax failure

it('indicates failure after ajax error', ->   expect($.fn.on).toHaveBeenCalledWith('ajax:error',                                        '.some_selector',                                        somecode.failure_func); 

Testing Ajax was called from custom event

it('indicates ajax call from custom event', ->   expect($.fn.on).toHaveBeenCalledWith('myCustomEvent',                                        '.some_selector',                                        somecode.custom_func); 
Read More

Friday, May 6, 2016

Xamarin UWP custom CommandBar

1 comment

I'd like to create a custom CommandBar for the UWP part of my Xamarin project.

I want to make a logo on a background color. The only way to get this, is making a visualbrush or grid, to contain both the background color and the picture.

I've read it can be done like so;

<Window.Resources>     <VisualBrush x:Key="myBrush">         <VisualBrush.Visual>             <Grid>                 <Rectangle Fill="Red" />                 <Image Source="troll.png" />             </Grid>         </VisualBrush.Visual>     </VisualBrush> </Window.Resources> 

I need to add this commandbar during runtime like so:

var _globalAppBar = new CommandBar(); _globalAppBar.Background = [Link  to above layout] 

Questions:

  1. How or where can I add this in my Xamarin UWP project? Add a XAML file?

  2. How can I link during run time to the layout? Or is there a better way to do this?

0 Answers

Read More

Writing Tables in Torch to file

Leave a Comment

I am trying to save some tables of strings to files in Torch. I have tried using this Torch extension by Deepmind: hdf5.

require 'hdf5' label = {'a', 'b','c','d'}  local myFile = hdf5.open(features_repo .. 't.h5', 'w') myFile:write('label', label) myFile:close() 

I am getting the error:

/home/user/torch/install/bin/luajit: ...e/user/torch/install/share/lua/5.1/hdf5/group.lua:222: torch-hdf5: writing data of type string is not supported 

Torch Tensors are written to file as intended.

I have also tried using matio to write to mat files (for MatLab). I am getting this error:

bad argument #1 to 'varCreate' (cannot convert 'number' to 'const char *') 

0 Answers

Read More

Android - Getting Contact List with street addresses but no low value ones like Skype where the address is only a city and state

Leave a Comment

I got a cursor retrieving all the contacts on the app that have a street address. This cursor is then passed into an Adapter. So far so good. Except I also get a bunch of low value contacts (mostly from Skype) that only have a State/Country info. Is there an easy way to modify the URI to skip those?

public Cursor getDirectoryList (CharSequence constraint)  {          String[] selectionArguments = { "%"+constraint.toString()+"%" };         String selection = ContactsContract.CommonDataKinds.StructuredPostal.DISPLAY_NAME + " like ?";          Uri uri = ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_URI;         String sortOrder = ContactsContract.CommonDataKinds.StructuredPostal.DISPLAY_NAME + " COLLATE LOCALIZED ASC";         Cursor cr = getContentResolver().query(uri, null, selection, selectionArguments, sortOrder);          return cr;     } 

1 Answers

Answers 1

You can modify the selection and/or selectionArgs to specify more criteria, such as ensuring that the Street field is not null:

String selection =     ContactsContract.CommonDataKinds.StructuredPostal.DISPLAY_NAME + " like ? AND " +     ContactsContract.CommonDataKinds.StructuredPostal.STREET + " IS NOT NULL"; 

This is just SQL, so specify as many fields from ContactsContract.CommonDataKinds.StructuredPostal, and whatever conditions on them, that you want.

Read More

How to mimic Google Maps' bottom-sheet 3 phases behavior?

Leave a Comment

Background

I'm assigned to make a UI that behaves similar to how Google Maps shows a bottom-sheet for a found result.

It has 3 different phases:

  1. bottom content. the upper area is still touchable and won't scroll anything at the bottom
  2. full screen content, while the upper area has a large header.
  3. full screen content, while the upper area has just the toolbar.

Here's what I'm talking about on Google Maps :

enter image description here

The problem

Thing is, the bottom sheet isn't a part of the design library yet (though it was requested, here) .

Not only that, but the UI seems quite complex, and need handling of the toolbar on multiple phases.

What I've tried

I've found a good (enough) library for bottom sheet (here), and added content to its fragment sample, to have about the same views as shown on material design samples (like here), to have a CollapsingToolbarLayout that will take care of phases 2+3.

In the app I'm making, I also have to move an icon as you scroll, but I think that if I succeed with the rest, this should be easy. Here's the code:

fragment_my.xml

<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout     android:id="@+id/main_content"     xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:app="http://schemas.android.com/apk/res-auto"     android:layout_width="match_parent"     android:layout_height="match_parent">      <android.support.design.widget.AppBarLayout         android:id="@+id/appbar"         android:layout_width="match_parent"         android:layout_height="@dimen/detail_backdrop_height"          android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">          <android.support.design.widget.CollapsingToolbarLayout             android:id="@+id/collapsing_toolbar"             android:layout_width="match_parent"             android:layout_height="match_parent"              app:contentScrim="?attr/colorPrimary"             app:expandedTitleMarginEnd="64dp"             app:expandedTitleMarginStart="48dp"             app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">              <ImageView                 android:id="@+id/backdrop"                 android:layout_width="match_parent"                 android:layout_height="match_parent"                 android:scaleType="centerCrop"                 app:layout_collapseMode="parallax"/>              <android.support.v7.widget.Toolbar                 android:id="@+id/toolbar"                 android:layout_width="match_parent"                 android:layout_height="?attr/actionBarSize"                 app:layout_collapseMode="pin"                 app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>          </android.support.design.widget.CollapsingToolbarLayout>      </android.support.design.widget.AppBarLayout>      <android.support.v4.widget.NestedScrollView         android:layout_width="match_parent"         android:layout_height="match_parent"         app:layout_behavior="@string/appbar_scrolling_view_behavior">          <LinearLayout             android:layout_width="match_parent"             android:layout_height="match_parent"             android:orientation="vertical"             android:paddingTop="24dp">              <android.support.v7.widget.CardView                 android:layout_width="match_parent"                 android:layout_height="wrap_content"                 android:layout_margin="@dimen/card_margin">                  <LinearLayout                     style="@style/Widget.CardContent"                     android:layout_width="match_parent"                     android:layout_height="wrap_content">                      <TextView                         android:layout_width="match_parent"                         android:layout_height="wrap_content"                         android:text="Info"                         android:textAppearance="@style/TextAppearance.AppCompat.Title"/>                      <TextView                         android:layout_width="match_parent"                         android:layout_height="wrap_content"                         android:text="@string/cheese_ipsum"/>                  </LinearLayout>              </android.support.v7.widget.CardView>              <android.support.v7.widget.CardView                 android:layout_width="match_parent"                 android:layout_height="wrap_content"                 android:layout_marginBottom="@dimen/card_margin"                 android:layout_marginLeft="@dimen/card_margin"                 android:layout_marginRight="@dimen/card_margin">                  <LinearLayout                     style="@style/Widget.CardContent"                     android:layout_width="match_parent"                     android:layout_height="wrap_content">                      <TextView                         android:layout_width="match_parent"                         android:layout_height="wrap_content"                         android:text="Friends"                         android:textAppearance="@style/TextAppearance.AppCompat.Title"/>                      <TextView                         android:layout_width="match_parent"                         android:layout_height="wrap_content"                         android:text="@string/cheese_ipsum"/>                  </LinearLayout>              </android.support.v7.widget.CardView>              <android.support.v7.widget.CardView                 android:layout_width="match_parent"                 android:layout_height="wrap_content"                 android:layout_marginBottom="@dimen/card_margin"                 android:layout_marginLeft="@dimen/card_margin"                 android:layout_marginRight="@dimen/card_margin">                  <LinearLayout                     style="@style/Widget.CardContent"                     android:layout_width="match_parent"                     android:layout_height="wrap_content">                      <TextView                         android:layout_width="match_parent"                         android:layout_height="wrap_content"                         android:text="Related"                         android:textAppearance="@style/TextAppearance.AppCompat.Title"/>                      <TextView                         android:layout_width="match_parent"                         android:layout_height="wrap_content"                         android:text="@string/cheese_ipsum"/>                  </LinearLayout>              </android.support.v7.widget.CardView>          </LinearLayout>      </android.support.v4.widget.NestedScrollView>      <android.support.design.widget.FloatingActionButton         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:layout_margin="@dimen/fab_margin"         android:clickable="true"         android:src="@android:drawable/ic_menu_send"         app:layout_anchor="@id/appbar"         app:layout_anchorGravity="bottom|right|end"/>  </android.support.design.widget.CoordinatorLayout> 

MyFragment.java

public class MyFragment extends BottomSheetFragment {      @Nullable     @Override     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {         final View view = inflater.inflate(R.layout.fragment_my, container, false);         view.setMinimumHeight(getResources().getDisplayMetrics().heightPixels);         CollapsingToolbarLayout collapsingToolbar = (CollapsingToolbarLayout) view.findViewById(R.id.collapsing_toolbar);         collapsingToolbar.setTitle("AAA");         final Toolbar toolbar = (Toolbar) view.findViewById(R.id.toolbar);         final AppCompatActivity activity = (AppCompatActivity) getActivity();         activity.setSupportActionBar(toolbar);         activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);         //toolbar.setNavigationIcon(R.drawable.abc_ic_ab_back_mtrl_am_alpha);         toolbar.setNavigationOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View v) {                 NavUtils.navigateUpFromSameTask(getActivity());             }         });         final ImageView imageView = (ImageView) view.findViewById(R.id.backdrop);          Glide.with(this).load(R.drawable.cheese_1).centerCrop().into(imageView);         return view;     }   } 

BottomSheetFragmentActivity.java

public final class BottomSheetFragmentActivity extends AppCompatActivity {      protected BottomSheetLayout bottomSheetLayout;      @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_bottom_sheet_fragment);         bottomSheetLayout = (BottomSheetLayout) findViewById(R.id.bottomsheet);         findViewById(R.id.bottomsheet_fragment_button).setOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View v) {                 new MyFragment().show(getSupportFragmentManager(), R.id.bottomsheet);             }         });         bottomSheetLayout.setShouldDimContentView(false);         bottomSheetLayout.setPeekOnDismiss(true);         bottomSheetLayout.setPeekSheetTranslation(200);         bottomSheetLayout.setInterceptContentTouch(false);         bottomSheetLayout.setDefaultViewTransformer(new BaseViewTransformer() {             @Override             public void transformView(final float translation, final float maxTranslation, final float peekedTranslation, final BottomSheetLayout parent, final View view) {                 Log.d("AppLog", "translation:" + translation + " maxTranslation:" + maxTranslation + " peekedTranslation:" + peekedTranslation);             }         });     } } 

It almost works well. The only problem is the transition from #3 back to #2:

enter image description here

The question

What is wrong with the code? What can I do in order to achieve the required behavior?

2 Answers

Answers 1

Note: read the edits at the bottom


ok, I've found a way to do it, but I had to change the code of multiple classes, so that the bottom sheet would know of the state of the appBarLayout (expanded or not), and ignore scroll-up in case it's not expanded:

BottomSheetLayout.java

added fields:

private AppBarLayout mAppBarLayout; private OnOffsetChangedListener mOnOffsetChangedListener; private int mAppBarLayoutOffset; 

init() - added this:

    mOnOffsetChangedListener = new OnOffsetChangedListener() {         @Override         public void onOffsetChanged(final AppBarLayout appBarLayout, final int verticalOffset) {             mAppBarLayoutOffset = verticalOffset;         }     }; 

added function to set the appBarLayout:

public void setAppBarLayout(final AppBarLayout appBarLayout) {     if (mAppBarLayout == appBarLayout)         return;     if (mAppBarLayout != null)         mAppBarLayout.removeOnOffsetChangedListener(mOnOffsetChangedListener);     mAppBarLayout = appBarLayout;     mAppBarLayout.addOnOffsetChangedListener(mOnOffsetChangedListener); } 

onDetachedFromWindow() - added this:

    if (mAppBarLayout != null)         mAppBarLayout.removeOnOffsetChangedListener(mOnOffsetChangedListener); 

onTouchEvent() - added this:

      ...       if (bottomSheetOwnsTouch) {         if (state == State.EXPANDED && scrollingDown && mAppBarLayout != null && mAppBarLayoutOffset != 0) {             event.offsetLocation(0, sheetTranslation - getHeight());             getSheetView().dispatchTouchEvent(event);             return true;         }       ... 

Those were the main changes. Now for what sets them:

MyFragment.java

onCreateView() - added this:

    mBottomSheetLayout.setAppBarLayout((AppBarLayout) view.findViewById(R.id.appbar)); 

also added this function:

 public void setBottomSheetLayout(final BottomSheetLayout bottomSheetLayout) {     mBottomSheetLayout = bottomSheetLayout; } 

Now this is how the activity tells the fragment about the appBarLayout:

            final MyFragment myFragment = new MyFragment();             myFragment.setBottomSheetLayout(bottomSheetLayout);             myFragment.show(getSupportFragmentManager(), R.id.bottomsheet); 

Project is now available on Github:

https://github.com/AndroidDeveloperLB/ThreePhasesBottomSheet

Hope it doesn't have any bugs.


EDIT : the solution has bugs, sadly, so I won't mark this answer as the correct one:

  1. only works well on Android 6 and above. other have a weird behavior of showing the bottom sheet expanded for a tiny fraction of a time, each time when showing it.
  2. orientation changes do not save the state of the scrolling at all, so I've disabled it.
  3. rare issue of being able to scroll inside the bottom sheet's content while it's still collapsed (at the bottom)
  4. if a keyboard was shown before, the bottom sheet might get to be full screen when trying to be peeked.

If anyone can help with it, please do.


EDIT: For issue #1, I've tried adding a fix by setting the visibility to INVISIBLE when the bottom sheet isn't peeked yet, but it doesn't always work, especially if a keyboard is shown.


EDIT: for issue #1, I've found how to fix it, by just wrapping (in "fragment_my.xml") the CoordinatorLayout with any view that you wish to use (I used FrameLayout), and also put a full-sized view in it (I just put "View") , as such:

<FrameLayout     xmlns:android="http://schemas.android.com/apk/res/android"     android:layout_width="match_parent"     android:layout_height="match_parent">     <!--This full sized view, together with the FrameLayout above, are used to handle some weird UI issues on pre-Android-6 -->     <View         android:layout_width="match_parent"         android:layout_height="match_parent"/>      <...CollapsingToolbarLayout      ... 

It probably confused the bottomSheet when I had the CoordinatorLayout being its view. I've updated the project, but still, if there is any way to have a nicer solution, I'd like to know about it.


EDIT: Google has published in recent months its own bottomSheet class, but as I've found it has a lot of issues, so I can't even try it out.

Answers 2

Did you try this? http://android-developers.blogspot.in/2016/02/android-support-library-232.html?m=1 In here it says we can just specify a bottom sheet layout behaviour.

UPDATE:

Basically the link states-

By attaching a BottomSheetBehavior to a child View of a CoordinatorLayout (i.e., adding app:layout_behavior="android.support.design.widget.BottomSheetBehavior"), you’ll automatically get the appropriate touch detection to transition between five state:

STATE_COLLAPSED: this collapsed state is the default and shows just a portion of the layout along the bottom. The height can be controlled with the app:behavior_peekHeight attribute (defaults to 0) STATE_DRAGGING: the intermediate state while the user is directly dragging the bottom sheet up or down STATE_SETTLING: that brief time between when the View is released and settling into its final position STATE_EXPANDED: the fully expanded state of the bottom sheet, where either the whole bottom sheet is visible (if its height is less than the containing CoordinatorLayout) or the entire CoordinatorLayout is filled STATE_HIDDEN: disabled by default (and enabled with the app:behavior_hideable attribute), enabling this allows users to swipe down on the bottom sheet to completely hide the bottom sheet Keep in mind that scrolling containers in your bottom sheet must support nested scrolling (for example, NestedScrollView, RecyclerView, or ListView/ScrollView on API 21+). 

If you’d like to receive callbacks of state changes, you can add a BottomSheetCallback:

// The View with the BottomSheetBehavior    View bottomSheet = coordinatorLayout.findViewById(R.id.bottom_sheet);    BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);    behavior.setBottomSheetCallback(new BottomSheetCallback() {       @Override       public void onStateChanged(@NonNull View bottomSheet, int newState) {         // React to state change       }         @Override         public void onSlide(@NonNull View bottomSheet, float slideOffset) {          // React to dragging events      }    });   

While BottomSheetBehavior captures the persistent bottom sheet case, this release also provides a BottomSheetDialog and BottomSheetDialogFragment to fill the modal bottom sheets use case. Simply replace AppCompatDialog or AppCompatDialogFragment with their bottom sheet equivalents to have your dialog styled as a bottom sheet.

Read More