Brian Hann https://brianhann.com Thu, 14 Dec 2017 17:57:52 +0000 en-US hourly 1 87222886 UI-Grid and Multi-Select https://brianhann.com/ui-grid-and-multi-select/ https://brianhann.com/ui-grid-and-multi-select/#comments Tue, 01 Dec 2015 17:23:28 +0000 https://brianhann.com/?p=732 In a previous post we explored how to use dropdown widgets with UI-Grid. That’s great if your model allows just one value, but what if you want to allow multiple values? Here’s an example: you’ve got a list of people who can each speak one or more languages. Your users need to be able select multiple […]

The post UI-Grid and Multi-Select appeared first on Brian Hann.

]]>
In a previous post we explored how to use dropdown widgets with UI-Grid. That’s great if your model allows just one value, but what if you want to allow multiple values?

Here’s an example: you’ve got a list of people who can each speak one or more languages. Your users need to be able select multiple options from a list of languages for each person in your data set. How do you do it?

Thankfully, UI-Select already has multi-select built in. All we need to do is take the code from our previous post and tweak it a bit.

Tweak the Theme

First off, it doesn’t look like we can use the selectize theme for multi-select. We’ll have to use select2. This is as simple as swapping out the theme property in our ui-select directive: theme="select2".

That only changes the theme; it doesn’t enable multi-selection. In order to do that we need to also add the multiple property.

We’ll also need to swap out our reference to the selectize CSS for select2’s: https://cdnjs.cloudflare.com/ajax/libs/select2/3.4.5/select2.min.css

This will have our select box looking like this: Multi-select example

Comma Separated Values

Next, in our grid we want to see the values separated by commas. We can create a custom cell template that joins the column’s field with the join operator:

<div class="ui-grid-cell-contents">
  {{ COL_FIELD.join(', ') }}
</div>

There’s other ways you could choose to display the selected values, of course. Comma-separated is easy to implement though.

Change the Placeholder

Finally, we need to make sure the selected options show up properly. Inside the ui-select-match directive we change {{ blah }} to {{ $item }}. Our final UI-Select markup looks like this:

<ui-select-wrap>
  <ui-select
    multiple
    ng-model="MODEL_COL_FIELD"
    theme="select2"
    ng-disabled="disabled"
    append-to-body="true">
    
    <ui-select-match placeholder="Choose...">{{ $item }}</ui-select-match>
    <ui-select-choices repeat="item in col.colDef.editDropdownOptionsArray | filter: $select.search">
      <span>{{ item }}</span>
    </ui-select-choices>
  </ui-select>
</ui-select-wrap>

 

Example

Here’s our updated plunker:

Have questions or suggestions? Leave them in the comments below!




Sign Up

Like what you read? Sign up for more! I love sharing tips, tricks, and methods to make web development faster and easier. And believe me, I hate spam just as much as you do.

The post UI-Grid and Multi-Select appeared first on Brian Hann.

]]>
https://brianhann.com/ui-grid-and-multi-select/feed/ 7 732
What to Do When Your AngularJS Project Goes Wrong https://brianhann.com/what-to-do-when-your-angularjs-project-goes-wrong/ https://brianhann.com/what-to-do-when-your-angularjs-project-goes-wrong/#comments Fri, 02 Oct 2015 12:07:00 +0000 https://brianhann.com/?p=677 It all started with such promise. You carefully chose how to structure your application. You picked a build system, or not, based on your experience and the community. You planned, and thought, and planned some more. But then something happened: you made a choice. It seemed like the right choice at the time. You were sure! Well, pretty […]

The post What to Do When Your AngularJS Project Goes Wrong appeared first on Brian Hann.

]]>
It all started with such promise.

You carefully chose how to structure your application. You picked a build system, or not, based on your experience and the community. You planned, and thought, and planned some more.

But then something happened: you made a choice. It seemed like the right choice at the time. You were sure! Well, pretty sure. I mean, how bad could it be if you were wrong, right? … Right?

 

AngularJS spaghetti code

Delicious when eating, not when coding.

Your app started to feel a little funky after that choice: less clean; less immaculate.

The funkiness increased each time you added more code. After a while it all started to congeal together, like spaghetti noodles that have sat out too long.

You couldn’t tell where your good choices started and where your bad ones began.

What do you do?

300 !@#!&(* Controllers?!

I recently came across a question on the Angular google group asking for help with an app where the author was trying to handle synchronous AJAX calls.

That’s not the part that stuck out to me. Here’s what did:

… I have created almost 300 controllers …

I had to do a double take. Three hundred controllers?  Why? What use case could possibly merit that?

Am I an old fogey developer, out of touch with the masses? Maybe separating your logic out into hundreds of tiny pieces is the wave of the future.

Somehow I don’t think so.

First I want to apologize to the person who asked this question.

I’m not ridiculing or chastising you. I seriously want to help you and anyone else who’s gotten themselves into a pickle when creating AngularJS applications. Let’s be honest: that’s all of us.

User frustration

I have written some absolutely terrible Angular apps that are still in active use in our company. I was new, and learning, and my users bore the brunt of my mistakes. Sorry, users!

Stop, and Reflect

When you’re writing applications and you’ve dug yourself into a hole, sometimes the only thing you can do is start digging another hole. The business won’t pay for you to trash your app and fix your mistakes.

But before you do, it’s important to take a moment for reflection. If you pause for reflection you will deepen the learning experience. The choices, decisions, and process that brought to this place will become more concrete.  And then you’ll know that you can make different decisions in the future.

Ask yourself some important questions.

  • Is this where I wanted to be?
  • How did I get here?
  • What choices did I made?
  • Would I make the same choices over again?
  • What did I choose to not inform myself of or read about? (Best practices, new tools, old paradigms, etc)
  • If there were options, how did I compare them?
  • How long did I take to make the decision? Was it a snap judgment or a drawn-out process?

It may be easier to work backwards from where you are now, until you reach the point of inflection where your decision train jumped the track. But if that feels overwhelming, just ask the questions above.

It’s important not to critically follow your decision breadcrumb trail, questioning yourself at every step.

Instead, what you want is better self-understanding: to see how you make decisions; whether you’re impatient or controlled, risk-adverse or venturesome, methodical or chaotic.

None of these character traits is good or bad, but if you know how to tend to act you can self-modulate in advance, protecting yourself from making similar mistakes that arise from your behavior patterns.

Be Gentle

When you do this sort of reflection it’s important to be gentle with yourself. Beating yourself up and calling yourself names in your head is absolutely not going to help you learn and improve.

Accept the situation for what it is: a chance to learn and grow. You will be a better developer for this experience. It is the uncomfortable places that stretch us where we grow the most.

How Did He Get Here?

Our author above didn’t start with 300 controllers.

They most likely grew our of a combination of two things:

  1. A decision that single controllers were the right place to put distinct bits code
  2. A whole bunch of distinct bits of code

Each time the author had a new thing to add to his app, he fired up ol app.controller() and went to town.

At some point he had 5 controllers handling some parts of his application. Perhaps they were all centered around a certain section and he needed to jump to another. Bing! five more controllers. The process continues as he grows his app: 20 controllers, 50, 100, 200.

What the author is seeing and saying to himself is not “man, that’s a lot of controllers!”, rather “man, that’s a lot of work and business logic!”.

Do It Different

Do it differentlyHow would you handle this situation differently? What are the patterns, best practices, and experiences that you can apply to this situation?

Could you use generators to create controllers? If you can make them through simple configuration then they should be generate-able as well.

If they are configurable then maybe composing different pieces together with nested views in UI-Router could be the best thing.

If the controllers represent different little widgets then directives are probably the answer. And you can always stack directives as well. You can create generic directives and have more specific ones stack functionality.

Do you have another idea? Share it in the comments section below!

 

The post What to Do When Your AngularJS Project Goes Wrong appeared first on Brian Hann.

]]>
https://brianhann.com/what-to-do-when-your-angularjs-project-goes-wrong/feed/ 1 677
UI Grid 3.0 Released! https://brianhann.com/ui-grid-3-0-released/ https://brianhann.com/ui-grid-3-0-released/#comments Mon, 20 Jul 2015 17:08:17 +0000 https://brianhann.com/?p=686 Last week UI Grid finally had its first stable release. This has been a long, long (too long) road. It started about 2 years ago as a rewrite of ng-grid, which was itself an AngularJS port of SlickGrid. And now UI Grid is stable! Well, almost… The core of UI Grid is all stable, but some of the […]

The post UI Grid 3.0 Released! appeared first on Brian Hann.

]]>
Last week UI Grid finally had its first stable release.

This has been a long, long (too long) road. It started about 2 years ago as a rewrite of ng-grid, which was itself an AngularJS port of SlickGrid. And now UI Grid is stable!

Well, almost… The core of UI Grid is all stable, but some of the features are a little behind. More on that later.

Want to give UI Grid a try? Head on over to to GitHub repo for installation instructions, and visit the Tutorials to learn how to use it.

Thank You

The team and I really want to thank the community for being so great, helpful, and patient with us. I don’t think any of us thought that this project would see so much use, and it’s been a real battle to make time to give you all the support you deserve!

Despite the slow timetable, you all have been super supportive, and that means a ton. Thank you.

Lessons Learned

This is the largest Open Source project I’ve worked on, and I’ve learned a ton. Here’s a few of the most important ones:

1. Start small, even with a rewrite

Our assumption was that because ng-grid had a bunch of features included (grouping, filtering, etc), we needed to include them all. Bzzzt, wrong!

We would have served the community better by getting a small release out the door with little functionality rather than trying to cram in everything v2.x had. This greatly lengthened our time-to-release.

We also ended up including more features that ng-grid did not have.  We should have scheduled those for a later release and really focused on getting the core stable and complete.

2. Schedule Responding to User Issues

Responding to issues on GitHub has largely been a catch-as-catch-can operation. I’ve done it when I can make time, and try to hit as many issues as I can.

Instead what I should have done is make a regular time-based schedule for organizing and responding to issues. This would make sure more people get their problems heard, understood, and solved.

3. Set up Style Guide in Advance

We all have our own style when it comes to writing code, and that’s cool, but for a large project having a consistent style is really important.  Debugging issues and reading 3 or 4 different ways of writing blocks, variable definitions, etc. gets really tiring.

We should have set up JSCS in advance, instead of late into the rewrite.

4. Trick People into Helping

OK not really.

But getting help is a must for a larger project. Our team has shrunk and grown multiple times over the past few years. Having multiple people means we can cover each other and help our users faster.

We’re incredibly lucky to have people who contribute on such a regular basis, those in the community included.

What’s Next

As I said in the beginning, the core of UI Grid is stable but some features are aren’t. They’re still alpha or beta. You can find a full list of the features on GitHub.

We plan to keep tackling bugs, increasing stability, and addressing new releases of Angular, including Angular 2.

Live Q&A

With 3.0 released, we want to give the chance for the community to ask any and all questions of the team. We also want to know what you’re doing with UI Grid, and what you interested in for the future.

We’ll be doing a live Q&A session on Saturday, August 8th @ 12:00PM CDT. If you want reminders and a link to the recording once it’s over, head on over to the event page.

UI Grid

Or if you just want to join the event live you can go to the Hangout page.

We look forward to seeing you all there!

The post UI Grid 3.0 Released! appeared first on Brian Hann.

]]>
https://brianhann.com/ui-grid-3-0-released/feed/ 2 686
4 Great Resources for AngularJS UX https://brianhann.com/4-great-resources-for-angularjs-ux/ https://brianhann.com/4-great-resources-for-angularjs-ux/#comments Mon, 13 Jul 2015 14:24:41 +0000 https://brianhann.com/?p=624 If you write Angular apps you spent a lot of your time making decisions for your users. You decide what they will see, and when. You decide how the app responds to their interactions. I often struggle knowing which is the right decision. Do I put this widget on the left or the right? Do I […]

The post 4 Great Resources for AngularJS UX appeared first on Brian Hann.

]]>
If you write Angular apps you spent a lot of your time making decisions for your users.

You decide what they will see, and when.

You decide how the app responds to their interactions.

I often struggle knowing which is the right decision.

  • Do I put this widget on the left or the right?
  • Do I only show one at a time, or multiple?
  • Do I separate things into different sections or have a long page where it’s all available at once?

Like most developers, I spend time seeing what others are doing and try to cherry-pick the good stuff.  What follows are some resources that I’ve come across that really help me make better decisions.

Better decisions mean by application functions better for my users. It also means it’s easier to understand, which means less support calls for our Help Desk (yay!).

I’m not perfect, of course. I still make wrong decisions. But I think I make them less! And hopefully these resources help you to make better design and user experience decisions when you create Angular apps.

Pressed for time? I’ve compiled a list of 20 take-aways from these resources. These are the things that I’ve found most helpful, and most impactful for me. Interested in checking it out? Get access to the 20 UX Take-Aways PDF .

User Onboarding

User OnboardingSamuel Huelick’s User Onboarding takes you into the guts of how other apps and sites treat new users.

Sam takes a given site or application and deconstructs their “onboarding” process into steps. Each step has a screenshot that he annotates with what’s good, what’s bad, and suggestions for improvement.

This is insanely useful where you’re creating new Angular apps, or upgrading old ones. You can learn loads from the mistakes that others have made, and from their successes as well. Sam does a great job of extracting those mistakes and successes, and processing them for you.

There’s a lot to absorb here, so I suggest you pick a couple that sound interesting and take notes. My favorites so far are:

Don’t Make Me Think

Don't Make Me ThinkDon’t Make Me Think by Steve Krug is a modern classic. Classic in the sense that the original version is 15 years old, and it’s still perfectly relevant today.

Steve’s overarching principle, what he calls the First Principle of Web Usability, is this:

Your user shouldn’t have to think when they land in your app.

The user’s next step should be self-evident.

Imagine the most technologically helpless person you know, and they’re using an Angular app you wrote. If you give them a simple task, will they know what to do? Yea, my users wouldn’t either.

And you know what? That’s really difficult. And it takes time. I’m not saying that you need to dumb everything down and cater to the lowest common denominator if you’re writing tools for software engineers, but here’s the thing:

Less decisions mean less mistakes.

The fewer choices you allow your user to make, the less likely they are to choose the wrong one. I’ve seen this time and time again in our business.

We unfortunately rely on many 3rd-party applications which give far too many options. We only want one type of exported file. The app allows 6. How many different types do you think we have sent in?

Yea, you got it.

Reduce decisions. Don’t make your users think. They’ll thank you and you’ll thank yourself.

Badass: Making Users Awesome

Badass: Making Users AwesomeKathy Sierra’s book, Badass: Making Users Awesome, was pure enlightenment for me.  It reframed how I think, at a core level:

  • What does my user really care about doing, and becoming? Kathy calls this their compelling context.
  • If I’m working on a business app, how does the app help my users achieve their compelling context?
  • What concepts does my user need to know, and what I can cut?
  • When do I introduce things so that I don’t overwhelm, bore, or alienate my users?

Even though a lot of my time is spent creating line-of-business apps, I still found this book incredibly relevant.

At our business we have several applications being used by employees you could lovingly refer to as “low-tech”. By that I mean that email is a new concept for them.

That’s not necessarily a bad thing, but it’s a hurdle for an application developer. How do I need to design, structure, and teach the applications I write so that I don’t frustrate my users, and don’t frustrate myself by watching them struggle?

My users don’t really care about being “good” at using my apps. They want to be “good” at their jobs. They want to advance. and grow (most of them anyway…).

The best thing I can do is create apps that meet them where they’re at. That makes the app more useful to them, and it reflects better on me.

What about you? Can you take what Kathy teaches and significantly improve your applications, while helping your users? I think so. Absolutely.

GoodUI

GoodUI

This site, from Jakub Linowski, is a running list of suggestions to make UIs easier to use. It also focuses on conversion rates. That may or may not apply to what your’re creating. If not, there’s still lots of great tactics that you should note. Even better, add them to a Best Practices notebook. You have one of those, right?

Each suggestion has an example sketch of what it looks like to follow and not follow it. This demonstration makes it easy to skim and absorb the suggestions quickly. There’s a text explanation for each of them, but most can be understood just by the examples.

Honestly a lot of these tips should be Common Sense. But I think that I could take almost every suggestion and find a project where I’ve gone the other way.

Has that ever been the right choice? Probably not.

Some tips that I have found helpful:

Summary

Those are your four resources, Mr or Mrs (or Ms!) Angular developer. You might be asking yourself:

Why only four…?

Here’s the answer:

There’s so much content in all of these that more would be superfluous.

I’ve gone through each of these resources in depth, and I find myself going back to them time and time again.

My brain needs to refresh. I need to continue building patterns, and remembering the things that are going to help my users the most.

diveMy suggestion to you is to start diving in.

Take notes (yes, get a pen or file up the ol’ text editor) of what you can apply right now in the Angular applications that you’re writing.

That’s going to have the largest impact on your, and your users.

Happy learning!

Interested in what I personally found most helpful? Check out my 20 UX Resource Take-Aways download below. Enter your email address and I’ll send you a link to my Bonus Content area where you can find this and other helpful resources.

I hate spam. I’ll never send you anything I don’t think is extremely helpful.

20 UX Resource Take-Aways PDF

Have a suggestion, question, or comment about the resources above, or on writing Angular applications with your users in mind? Leave a comment below ↓

The post 4 Great Resources for AngularJS UX appeared first on Brian Hann.

]]>
https://brianhann.com/4-great-resources-for-angularjs-ux/feed/ 4 624
Understanding Dgeni’s Pipeline: Live Infographic https://brianhann.com/understanding-dgeni-pipeline/ https://brianhann.com/understanding-dgeni-pipeline/#respond Thu, 02 Jul 2015 17:14:26 +0000 https://brianhann.com/?p=605 I was having a heck of a time understanding how Dgeni‘s processors all work together and what order they run in, so I created a Dgeni package to automatically generate a doc that shows all the processors on a single page, and gives you information on what they do. What’s Dgeni? If you’re unfamiliar with […]

The post Understanding Dgeni’s Pipeline: Live Infographic appeared first on Brian Hann.

]]>
I was having a heck of a time understanding how Dgeni‘s processors all work together and what order they run in, so I created a Dgeni package to automatically generate a doc that shows all the processors on a single page, and gives you information on what they do.

What’s Dgeni?

If you’re unfamiliar with Dgeni, then you’re in good company. It’s being used by a lot of major Angular projects (Angular itself, the Ionic Framework, Angular Materials, etc) but there’s not a whole lot of information out on it. That’s too bad because it’s a great tool.

Dgeni takes documentation whether formatted comments you put in your code or separate files that you specify, and generates usable organized docs from it. You have to decide exactly what you want but Dgeni makes the process of hooking everything together much easier than doing it all by hand.

If you’re interested in learning more about Dgeni, let me know and I’ll give you a heads up when I have more how-tos, guides, and tutorials out.

 

What’s below is sort of a “live” infographic: you can filter by package and click around to read the docs (some of which need some more information).

Note: Some of the JavaScript doesn’t work in an iframe, so check out the original page for a better view.




Found this helpful? I'd like to send you more!

I love sharing tips, tricks, and methods to make web development faster and easier. And believe me, I hate spam just as much as you do.

The post Understanding Dgeni’s Pipeline: Live Infographic appeared first on Brian Hann.

]]>
https://brianhann.com/understanding-dgeni-pipeline/feed/ 0 605
Easily Import Spreadsheets into UI-Grid https://brianhann.com/easily-import-spreadsheets-into-ui-grid/ https://brianhann.com/easily-import-spreadsheets-into-ui-grid/#comments Wed, 24 Jun 2015 22:23:51 +0000 https://brianhann.com/?p=565 Your desktop spreadsheet tools need to handle all sorts of different proprietary file formats. Why should your web tools be different? Unfortunately in the past it has been pretty much impossible to get JavaScript in the browser to be able to read XLS, ODS, and all the others. Thankfully, these days there’s libraries that will do the heavy lifting for […]

The post Easily Import Spreadsheets into UI-Grid appeared first on Brian Hann.

]]>
Your desktop spreadsheet tools need to handle all sorts of different proprietary file formats. Why should your web tools be different?

Unfortunately in the past it has been pretty much impossible to get JavaScript in the browser to be able to read XLS, ODS, and all the others. Thankfully, these days there’s libraries that will do the heavy lifting for us. In this post we’ll look at importing data from spreadsheets right into your UI-Grid with SheetJS.

Bonus: Want to export grid data as a spreadsheet file, right in the browser, no servers involved? [leadbox_link interest=”ui-grid” title=”Get the Guide” text=”Enter your email to get the bonus content link.” button=”Send it to Me!”]Enter your email and get a link to the bonus area[/leadbox_link]. It has a how-to guide that will walk you through it.

SheetJS: In-Browser Spreadsheets

SheetJS is a github organization that provides several node.js and browser-based libraries for reading and writing spreadsheets, in a lot of different formats.  Just to name a few, it can handle:

  • XLS
  • XLSX
  • CSV / TSV
  • ODS

For me this covers all my use cases. Our office is split into people using current MS products, people with Open Office, and tools that export old XLS files.

The specific libraryies we’ll be using is js-xlsx, which will handle Excel files, and the add-on ods.js that will handle Open Office files.

Example App

We’re going to show off a little example that will fill out a UI-Grid instance from a spreadsheet file the user selects.

First we need to make a way to select the file. We’ll do this by transcluding content into the grid.

Want to read more about UI-Grid and transclusion. Check out this post: UI-Grid: The Easiest Customization You’ll Ever Write

<div ng-controller="MainCtrl as vm">

<div id="grid1" ui-grid="vm.gridOptions" class="grid">
  <div class="grid-msg-overlay" ng-show="!vm.gridOptions.data.length">
    <div class="msg">
      <div class="center">
        <span class="muted">Select Spreadsheet File</span>
        <br />
        <input type="file" accept=".xls,.xlsx,.ods" fileread opts="vm.gridOptions" multiple="false" />
      </div>
    </div>
  </div>
</div>

</div>

UI-Grid will bring all the content inside <div ui-grid></div> into itself, at its own scope. We can use this to display a “Select File” message when there’s no rows.  Inside this message we have an <input type="file"> that accepts spreadsheet files. It also has a fileread attribute. That’s our directive that will do the heavy lifting.

The directive needs to do three things:

  • Listen for changes to the file input and handle selected files
  • Read the file contents in and process them with js-xls
  • Turn the output from js-xls into column definitions and data rows for UI-Grid

We can use the change event handler to be notified when the user selects a file. When that happens we will create a new FileReader  that will read the file in as a binary string, and we’ll process the data in the onload handler. The directive initially looks like this:

.directive("fileread", [function () {
  return {
    link: function ($scope, $elm, $attrs) {
      $elm.on('change', function (changeEvent) {
        var reader = new FileReader();
        
        reader.onload = function (evt) {
          var data = evt.target.result;
        };
        
        reader.readAsBinaryString(changeEvent.target.files[0]);
      });
    }
  }
}]);

Now we have data in our onload handler which has the contents of the spreadsheet file. We next need to process data with js-xlsx.

.directive("fileread", [function () {
  return {
    scope: {
      opts: '='
    },
    link: function ($scope, $elm, $attrs) {
      $elm.on('change', function (changeEvent) {
        var reader = new FileReader();
        
        reader.onload =function (evt) {
          $scope.$apply(function () {
            var data = evt.target.result;
            
            var workbook = XLSX.read(data, {type: 'binary'});
            
            var headerNames = XLSX.utils.sheet_to_json(
                                workbook.Sheets[workbook.SheetNames[0]],
                                { header: 1 }
                              )[0];
            
            var data = XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);
            
            $scope.opts.columnDefs = [];
            headerNames.forEach(function (h) {
              $scope.opts.columnDefs.push({ field: h });
            });
            
            $scope.opts.data = data;
            
            $elm.val(null);
          });
        };
        
        reader.readAsBinaryString(changeEvent.target.files[0]);
      });
    }
  }
}]);

Here we’ve added a scope property to the directive to allow us to pass in the grid’s options object. We’ll use this to get our column defs and rows into the grid.

XLSX.read(data) returns a workbook object to us. This contains two properties, one is an array called SheetNames that has the list of sheets in the workbook. The other property is Sheets which is a dictionary of sheet names whose value is their Sheet object.

The Sheet object, then,  is just a dictionary of of cell positions whose value is the cell information (value, formatting, etc). It kinda looks like:

{
  A1: {
    t: 's', // Cell type. "s" for "String"
    v: 'my raw value'
    w: 'my formatted value'
  },
  A2: {
    t: 's',
    v: 'Another value',
    w: 'Another value'
  }
}

This would be a pain to manually process. Luckily there’s a utility method in XLSX.utils called sheet_to_json. We can pass it the sheet and get the data back as a JSON object. It will even use the first row as the column names for the keys. Yay!

That’s our row data. We could stop here if we didn’t care about the column defs, but if we do we can use XLSX.utils.sheet_to_json again with the argument { header: 1 }. This will return a simple array of arrays where each element of the outer array is a row and each element of the inner arrays is the set of columns for that row. We can use the first row as our column names and iterate over each of them, creating a column def for each one.

At last, here’s our working demo!

Hopefully you have a spreadsheet sitting around. If not you can use this one. Save that link to your desktop/device and insert it into the demo above.

Summary

That’s about the long and short of it! You may have problems with specific file types; check out the SheetJS demo pages to see if your files work there, and do some debugging in your developer console to see what’s going on; js-xlsx is pretty good about giving useful exceptions.

Other things you could do with this:

  • Add a drag-and-drop target inside the grid.
  • Allow uploading multiple files and combine them all into one grid
  • Download a spreadsheet from UI-Grid directly in the browser (Let me know if you are interested in this one in the comments below and I’ll do another post).

Good luck! If you have any questions, comments, or suggestions please feel free to leave a comment. I try to respond to all of them as soon as I can.




Want to Export Files?

Enter your email to get a bonus how-to guide that will walk you through exporting XLSX files from UI-Grid.

The post Easily Import Spreadsheets into UI-Grid appeared first on Brian Hann.

]]>
https://brianhann.com/easily-import-spreadsheets-into-ui-grid/feed/ 34 565
Angular 1.4 Breaking Changes to Be Aware Of https://brianhann.com/angular-1-4-breaking-changes-to-be-aware-of/ https://brianhann.com/angular-1-4-breaking-changes-to-be-aware-of/#comments Thu, 11 Jun 2015 15:03:00 +0000 https://brianhann.com/?p=431 The Angular 1.4 release introduced a lot of breaking changes. You might have had trouble upgrading your 1.3 apps (if you even decided to try). There’s a good run-down on how to migrate your code in the Angular docs. We’ll cover some of the same ground here but also dive into some extra approaches for adjusting […]

The post Angular 1.4 Breaking Changes to Be Aware Of appeared first on Brian Hann.

]]>
The Angular 1.4 release introduced a lot of breaking changes. You might have had trouble upgrading your 1.3 apps (if you even decided to try).

There’s a good run-down on how to migrate your code in the Angular docs. We’ll cover some of the same ground here but also dive into some extra approaches for adjusting your code, as well as some cases I’ve run into where I’ve had to support both 1.3 and 1.4.

Jump to a section to read the changes.

 

$animate

JavaScript and CSS animations can no longer be run in
parallel.

In older versions, if both JS and CSS animations were detected they would be run at the same time. This won’t happen automatically any more, but you can use $animateCss to create CSS-based animations in your code.

 

The function params for $animate.enabled() when an
element is used are now flipped. This fix allows the function to act as
a getter when a single element param is provided.

I ran into this guy when trying to upgrade UI-Grid to Angular 1.4. The fix is to just swap the order of your parameters, unless you have to support multiple versions. In that case you’ll have to do some fun (not) version checking:

if (angular.version.major > 1 || (angular.version.major === 1 && angular.version.minor >= 4)) {
  $animate.enabled($elm, false);
}
else {
  $animate.enabled(false, $elm);
}

 

In addition to disabling the children of the element,
$animate.enabled(element, false) will now also disable animations on
the element itself.

Just something to keep in mind. It’s unlikely that you were implicitly relying on this, but it could bite you.

 

Animation-related callbacks are now fired on
$animate.on instead of directly being on the element.

If you were listening for events on your element with something like  element.on('$animate:before', ...) you’ll need to switch to $animate.on(element, 'enter', ...).

 

There is no need to call $scope.$apply or $scope.$digest inside of an animation promise callback anymore since the promise is resolved within a digest automatically (but a digest is not run unless the promise is chained).

Yay! This makes things a bit cleaner. It wouldn’t really hurt to keep any remaining $timeouts in your animation callbacks, but you don’t need them to trigger a $digest anymore.

 

When an enter, leave or move animation is triggered then it will always end any pending or active parent class based animations (animations triggered via ngClass) in order to ensure that any CSS styles are resolved in time.

This will probably help you out by not having multiple animations running when you don’t intend them.

 

Prior to this fix there were two ways to apply CSS animation code to an anchor animation. With this fix, the suffixed CSS -anchor classes are now not used any more for CSS anchor animations.

Animation anchoring lets you pair up elements that are in different structural parts of the application, like views, so that it appears that a single element is transitioning. You can read about it and see a demo in the Angular docs.

With this change you can just specify an .ng-anchor CSS class for your element (like .my-element.ng-anchor rather than having to make -anchor-suffixed extra rules.

Also note that you can use .ng-anchor-in and .ng-anchor-out if you want to target those phases of the animation.

 

If your CSS code made use of the ng-animate-anchor
CSS class for referencing the anchored animation element then your
code must now use ng-anchor instead.

They just renamed the class. If you want to support both 1.3.x and 1.4.x you could add an extra rule for.ng-anchor anywhere you have .ng-animate-anchor.

 

Partially or fully using a regex value containing
ng-animate as a token is not allowed anymore. Doing so will trigger a
minErr exception to be thrown.

This refers to using the classNameFilter() method in $animateProvider; ng-animate is essentially a reserved word. You can’t do this:

$animateProvider.classNameFilter(/my-animations ng-animate/);

but you could do this if you wanted to:

$animateProvider.classNameFilter(/ng-animate-mine/);

 

 

$animateCss

The $animateCss service will now always return an object even if the animation is not set to run.

You won’t need to check and see if $animateCss(element, { ... }); has returned anything any more.

 

 

$cookies

$cookies no longer exposes properties that represent the current browser cookie values. Now you must explicitly the methods described above to access the cookie values. This also means that you can no longer watch the $cookies properties for changes to the browser’s cookies.

The $cookies API originally polled for changes, synchronizing the local set with the browser. This was expensive and had out-of-sync issues.

Now you need to use the API methods to access the cookies:

var allCookies = $cookies.getAll();

 

 

filter

Previously, the filter was not applied if used with a non array. Now, it throws an error. This can be worked around by converting an object to an array, using a filter such as https://github.com/petebacondarwin/angular-toArrayFilter

The “filter” filter would fail silently if you used it on an object. As explained in the initial bug report  this would fail silently:

<div ng-repeat="item in object | filter:{display:true}">stuff</div>

You’d just see stuff repeated twice in your DOM with no explanation.

With 1.4.0 you’ll now get an error that looks like this: Expected array but received: {“display”:false,”foo”:”bar”}. Using PBD’s filter is a good way to make sure you’re using the proper object type with filter.

 

 

$http

transformRequest functions can no longer modify request headers … This behavior was unintended and undocumented, so the change should affect very few applications

The $http property  transformRequest can be used to alter a given request’s http request body. If you want to modify headers, the changelog says you can do it in your $http request with a headers function:

$http.get(url, {
  headers: {
    'X-Custom-Header': function(config) {
      return 'my-custom-value';
    }
  }
});

Or if you want more global alteration you can modify $httpProvider‘s header defaults:

$httpProvider.defaults.headers.common['X-Custom-Token'] = 'abc123';

 

 

limitTo

limitTo changed behavior when limit value is invalid.
Instead of returning empty object/array it returns unchanged input.

The limitTo filter creates a subset of an array, much like slice in vanilla JS. If you supply a bad value to limitTo, like a string or NaN, you’ll now get the unaltered array back rather than an empty array.

 

 

ngMessages

The ngMessagesInclude attribute is now its own directive and that must
be placed as a child element within the element with the ngMessages
directive.

Angular 1.3 introduced a nicer way to display form validation messages called ngMessages. You can read a nice intro in Pascal Precht’s blog post.

When displaying validation messages you would often need to use the same message over and over. This could lead too a less than DRY situation in your app. ngMessageInclude was a way to write a template for a validation message once and then include it in multiple places. It still exists; it’s just a directive instead of an attribute now:

<script type="script/ng-template" id="required-message">
  <ng-message when="required">
    This field is required!
  </ng-message>
</script>

<ng-messages for="myForm.username.$error">
  <div ng-message="minlength">...<div>
  <div ng-messages-include="required-message"></div>
</ng-messages>

You can read more about the change in Pasca’s follow-up on ngMessages.

 

 

ngOptions

When using ngOptions: the directive applies a surrogate key as the value of the <option> element. This commit changes the actual string used as the surrogate key. We now store a string that is computed by calling hashKey on the item in the options collection; previously it was the index or key of the
item in the collection.

This shouldn’t have too much effect on you, as you shouldn’t be relying on ngOptions‘ surrogate key. If you do then you’ll want to either add a track by expression to control how the surrogate key is defined, or just access the property that the select’s ng-model  is bound to.

Be Aware: If you were relying on the actual value of a select box in your unit tests, you’ll need to adjust for this. We ran into this with UI-Grid. Instead of $('.my-select-box').val() returning 5, you’ll get "number:5".

 

When iterating over an object’s properties using the (key, value) in obj syntax
the order of the elements used to be sorted alphabetically. … in practice this is not what people want and so this change iterates over properties in the order they are returned by Object.keys(obj).

If you were relying on this implicit ordering in ngOptions you’ll need to add a sorting filter.

 

Although it is unlikely that anyone is using it in this way, this change does change the behaviour of ngOptions in the following case:

  • You are iterating over an array-like object, using the array form of the ngOptions syntax (item.label for item in items) and that object contains non-numeric property keys.

In this case these properties with non-numeric keys will be ignored.

Hopefully you would not treat yourself (or your coworkers) so villainously, but there’s probably at least one person out there who has, at least unintentionally. If you find code with arrays that have extra properties and the repeater is iterating over them in this way, change it to the object iterator syntax: value.label for (key, value) in items.

 

 

ngRepeat

Previously, the order of items when using ngRepeat to iterate
over object properties was guaranteed to be consistent by sorting the
keys into alphabetic order.

Now, the order of the items is browser dependent based on the order returned from iterating over the object using the for key in obj syntax.

This is the same as the breaking change to ngOptions above. If you were relying on implicit sorting in ngRpeat when using an object as the source, then you’ll need to add your own sorting filter. Otherwise the elements in your repeater will come out in whatever order the user’s browser decides on.

Be Aware: The changelog says “…the best approach is to convert Objects into Arrays by a filter such as https://github.com/petebacondarwin/angular-toArrayFilter or some other mechanism, and then sort them manually in the order you need.”

The post Angular 1.4 Breaking Changes to Be Aware Of appeared first on Brian Hann.

]]>
https://brianhann.com/angular-1-4-breaking-changes-to-be-aware-of/feed/ 6 431
Make UI-Grid Take up The Whole Page https://brianhann.com/make-ui-grid-take-up-the-whole-page/ https://brianhann.com/make-ui-grid-take-up-the-whole-page/#comments Tue, 09 Jun 2015 19:45:59 +0000 https://brianhann.com/?p=441 Specifying heights in UI-Grid is a bit weird. Because a grid’s content is virtualized, you really have to tell the grid how tall you want it to be. If it has 100,000 rows in its data set, but you only want to see 20, or 400px worth, you have to tell it so. If you don’t give […]

The post Make UI-Grid Take up The Whole Page appeared first on Brian Hann.

]]>
Specifying heights in UI-Grid is a bit weird.

Because a grid’s content is virtualized, you really have to tell the grid how tall you want it to be.

If it has 100,000 rows in its data set, but you only want to see 20, or 400px worth, you have to tell it so. If you don’t give it a height then the grid will assume you want to see 10 rows, minimum.

Note that if you want to specify the height by giving a number of rows to show, you can use the minRowsToShow option.

If you want a deeper discussion, feel free to read A Hidden Grid Manifesto in the UI-Grid docs.

Full Page Grids

How about the situation where we want to make the grid take up the entire web page, height and width?

Luckily this can be done with just a little bit of CSS. We give the grid 100% of the page width, and then for the height we use a viewport percentage length. That’s where we specify the size of an element relative to the size of the viewport, and not relative to the element’s parent.

So if the viewport is 800px high and we say height: 100vh, our element will be 800px high. For the grid, in my experiments 98 looks about right, as 100 is a wee bit too big.

.grid {
  height: 98vh;
}

Example

You can see the result by opening this plunker. The grid takes up the whole viewport, and will resize nicely with the window. Great!




Sign Up

Like what you read? Sign up for more! I love sharing tips, tricks, and methods to make web development faster and easier. And believe me, I hate spam just as much as you do.

The post Make UI-Grid Take up The Whole Page appeared first on Brian Hann.

]]>
https://brianhann.com/make-ui-grid-take-up-the-whole-page/feed/ 3 441
UI-Grid and Row Animations https://brianhann.com/ui-grid-and-row-animations/ https://brianhann.com/ui-grid-and-row-animations/#comments Wed, 03 Jun 2015 16:44:29 +0000 https://brianhann.com/?p=408 When you add and delete rows in UI-Grid it can be a bit jumpy. All of a sudden, rows get shifted around and you’re not sure where you were. It’s like dropping a book on the ground and losing your place: Where the hell was I? In this post we’ll explore how to add animations […]

The post UI-Grid and Row Animations appeared first on Brian Hann.

]]>
When you add and delete rows in UI-Grid it can be a bit jumpy.

All of a sudden, rows get shifted around and you’re not sure where you were.

It’s like dropping a book on the ground and losing your place:

Where the hell was I?

In this post we’ll explore how to add animations to grid row operations to visually demonstrate what’s happening. The user should be able to tell what rows have just been added, and what rows are being removed.

Animating with CSS

CSS animations have been around a while. You might have used them on your own projects. If you haven’t, check out a couple links to see them in action.

In case you’re unfamiliar with the idea, animating with CSS offloads the hard work to the browser, which can optimize the operations and keep things looking smooth.

Animations also allow visual continuity. As the Google Material Design docs say:

Transitioning between two visual states should be clear, smooth, and effortless. A well-designed transition tells the user where to focus their attention.

Angular provides a module called ngAnimate which will automatically animate elements in directives like ng-repeat, ng-if, and ng-show if you provide the right CSS classes. Read about that in the Angular docs.

The Problem

Unfortunately there’s a problem when we want to use animations in UI-Grid. Because the grid’s content is virtualized, we are not adding and deleting DOM elements in a way that Angular can automatically manage.

Virtualization means that instead of rendering every row in your data set, the grid only renders what would be visible on the screen, plus a little bit extra as a hedge. Everything else is just wide empty space that you don’t see because you’re basically looking through a window at your data.

The Solution

To sum up so far, we fake having a big old data set on your screen, which means we can’t use the standard and easy animation method. How do we do the animations then? Fake them as well.

Angular’s  $animate service provides us with several methods for manually triggering animations. We’ll use two: addClass and removeClass. When you call these on an element Angular will apply some extra classes you can set up with CSS transitions.

We will fake the rows appearing and disappearing by watching for the data to change from within code that’s bound to the row. If the rendered row’s data changes to a new row, start the animation.

The animation itself for adding a row will set the row’s opacity to 0 initially, right when the data is changed, then it will transition to full opacity over one second. This will give the appearance of a new row fading in.

Let’s look at the CSS we’ll be using for adding rows:

.new-row {
  opacity: 0;
}

.new-row-remove {
  -webkit-transition: 1s linear all;
  -moz-transition: 1s linear all;
  -o-transition:1s linear all;
  transition: 1s linear all;
  opacity: 0;
}

.new-row-remove-active {
  opacity: 1;
}

The first class is the one we apply to new rows. We can do this with normal $elm.addClass('new-row'); this makes the row invisible initially. When we remove this class with $animate.addClass(element, 'new-row'), however, Angular will also first add the .new-row-remove class to start the animation, and at the end of the animation add the .new-row-remove-active class. Angular does this by parsing the styles the classes apply and pulling out the transition timing information. The result is that the row will transition from no opacity to full opacity over 1 second.

Enough talk, lets see the example:

Example

There’s a bit more here than we’ve talked about, but focus on the end result of clicking the “Add Rows” button. Remember that no elements are being created or destroyed. We’re just altering CSS and the data binding. So the rows appear to shift down, and the new rows appear to fade in. Best of both worlds, right?

Go ahead and play with the other settings. You can make the rows slide in, and alter the timing so it’s slow or fast. You can also change the easing function; see easings.net to see a bunch of different easing types in action.

Code for Adding Rows

We’ve seen the CSS. What about the JS? We said that we’d watch the data bound to the row and use $animate to change classes. Here’s what that looks like:

// In our grid options:
onRegisterApi: function (gridApi) {
  gridApi.grid.registerRowBuilder(function (row, gridOptions) {
    row.isNew = true;
  });
}

.directive('uiGridRow', function ($animate, $timeout) {
  return {
    priority: -1,
    link: function ($scope, $elm, $attrs) {
      $scope.$watch('row.entity', function (n, o) {
        if ($scope.row.isNew) {
          $elm.addClass('new-row');
          
          $timeout(function () {
            $animate.removeClass($elm, 'new-row');
          });
          
          $scope.row.isNew = false;
        }
      });
    }
  };
});

In our grid options we bind to the grid’s API and register what’s called a Row Builder. Row builders are functions that are executed on rows when they’re created. You can use them to extend row construction as you see fit. Here we just add a flag identifying the row as new.

We are stacking this directive on top of the existing uiGridRow directive. They will both run but ours runs at a lower priority so as not to affect the core directive.

In our link function we simply watch row.entity, which is the element from your dataset array. When it changed, we see if there’s an isNew property. If so, we add the new-row class, then remove it with $animate. Finally, we reset the isNew flag so the animation doesn’t run again during scrolling.

Q: Hey, in your $watch why aren’t you checking to see if n and o are unequal? Aren’t you running the animations too much?

Answer: No, not exactly. Every row initially has the isNew flag but on the initial $watch execution the entity hasn’t changed, so n and o are in fact equal. If we don’t allow the animation to run they when new rows are added the animations on ALL the visible rows will run at once, and that looks weird. Visually you don’t see the animations run as the row elements pop in when the grid starts, however.

Animated Row Deletions

The delete button on each row will perform the chosen animation, but backwards. We’ll use the delete-row class. Here’s the minimum amount of CSS:

.delete-row-add {
  -webkit-transition: 0.5s linear all;
  -moz-transition: 0.5s linear all;
  -o-transition: 0.5s linear all;
  transition: 0.5s linear all;
  opacity: 1;
}

.delete-row-add-active {
  opacity: 0;
}

So when the delete-row class is added, the row starts at opacity: 1 and over 0.5 seconds, transitions to opacity: 0.

The code for this is a bit different than what we’ve seen so far, however.  We have two problems:

  1. We can’t just remove the row when the button is clicked. We have to wait for the animation to run, THEN remove the row. Otherwise the row will just pop out of existence. The removeClass method returns a promise that is resolved after the animation completes so we can use that perform the removal.
  2. The scope isolation within the delete button’s grid cell prevents us from having access to our custom uiGridRow directive. We will need to emit an event from the cell that the row listens for.

Let’s see the code. Make sure to pay attention to the comments. They explain how the pieces work together.

// Stack the uiGridCell directive
.directive('uiGridCell', function () {
  return {
    priority: -1,
    link: function ($scope, $elm, $attrs) {
      // Expose the deleteRow function to the cell scope so our custom template
      //  can call it
      $scope.deleteRow = deleteRow;
      
      // When the delete button is clicked, emit a "delete-row" event and pass
      //   the row as an argument
      function deleteRow(row) {
        $scope.$emit('delete-row', row);
      }
    }
  } 
})

// In our stacked uiGridRow directive...
.directive('uiGridRow', function ($animate, $timeout, uiGridConstants) {
  return {
    priority: -1,
    link: function ($scope, $elm, $attrs) {
      // Listen for the above "delete-row" event
      $scope.$on('delete-row', function (evt, row) {
        // Start the animation for removing the row by adding the .delete-row class
        $animate.addClass($elm, 'delete-row')
          // ... the animation is done.
          .then(function () {
            // Remove the .delete-row class; we don't want any more animations to run.
            $elm.removeClass('delete-row');
            
            // We're not in $digest cycle currently, so we'll initiate a new one with $timeout.
            $timeout(function () {
              // Splice out the row from the data set. This will visually make it
              //   look like the rows shift up.
              var data = $scope.grid.options.data;
              data.splice(data.indexOf(row.entity), 1);
            });
          });
      });
    }
  }
});

That’s a bunch of lines but it gets us what we want. If you haven’t yet, check out deleting rows in the example above.

The rows fade or slide out and then the data shifts up. You’d never know the DOM elements were staying right where they are!

Where to Go From Here

What else can you do with animations, either in UI-Grid or your app? How can you help your users by providing meaningful transitions? What common operations do they (or you) find confusing?

Perhaps you could animate popping up modals, or filtering. Maybe transitions between sections of your application, or interactions with buttons, checkboxes and other elements. The sky’s the limit! Check out the Angular Material Demos to see how Google is encouraging the use of animations in web and mobile applications.

If you have any questions, please feel free to pop them in the comments below. I always respond as soon as I can. Also, for general UI-Grid talk join us on Gitter in the UI-Grid chat room. See you there!




Sign Up

Like what you read? Sign up for more! I love sharing tips, tricks, and methods to make web development faster and easier. And believe me, I hate spam just as much as you do.

 

The post UI-Grid and Row Animations appeared first on Brian Hann.

]]>
https://brianhann.com/ui-grid-and-row-animations/feed/ 6 408
UI-Grid and Dropdowns https://brianhann.com/ui-grid-and-dropdowns/ https://brianhann.com/ui-grid-and-dropdowns/#comments Fri, 22 May 2015 07:19:00 +0000 https://brianhann.com/?p=287 Dropdown-type widgets abound in the web development space. No matter what you call them: combobox, select box, multi-select, they all accomplish basically the same thing: selecting one or more items from many. Sure, a lot of them have extra bells and whistles like type-ahead, sorting, custom display, etc., but with all the extra comes extra […]

The post UI-Grid and Dropdowns appeared first on Brian Hann.

]]>
Dropdown-type widgets abound in the web development space.

No matter what you call them: combobox, select box, multi-select, they all accomplish basically the same thing: selecting one or more items from many.

Sure, a lot of them have extra bells and whistles like type-ahead, sorting, custom display, etc., but with all the extra comes extra pain.

You’ll find that pretty much none of the commonly-used dropdown tools have compatible APIs. Some have events they fire, some don’t. Some will let you extend them easily, others are very constrained.

When you combine all the different options out there with a need to put a dropdown in UI-Grid, the pain multiplies:

How do I get the dropdown to display right in the grid? How do I hook the dropdown up to my data?

And what if I spend a ton of time on this and it ends up not working?

I want to give you a quick fix that will work for a good number of your use cases.

In this post you’ll find a working example for using one of the popular dropdown widgets: UI-Select.

Why UI-Select?

Why use this one of all of the others? The short answer is that it can attach to the document body, rather than just inside its parent element. UI-Grid has to prevent visible overflow in a lot of places, which means that widgets that show potentially long lists (which dropdowns can do) can get cut off.

Placing the widget’s contents outside of the grid avoids that problem completely.

OK, So How Do I Use It?

We’ll cover a short series of steps to getting UI-Select working in our grid. For our example, we will have a bunch of data where each row is a user with a gender. When editing the gender we want to be able to select an option from a dropdown.

Here’s our steps:

  1. Set up the grid for editing
  2. Create a edit template for UI-Select, hooking it up to our grid rows
  3. Wrap our UI-Select template so we can handle events like hiding it when we click elsewhere on the grid or document

1. Grid Setup

Here’s what our grid options look like:

$scope.gridOptions = {
    rowHeight: 38,
    columnDefs: [
      { name: 'name' },
      { name: 'age', type: 'number' },
      {
        name: 'gender',
        editableCellTemplate: 'uiSelect.html',
        editDropdownOptionsArray: [
          'male',
          'female',
          'other'
        ]
      }
    ]
  };

In our gender column we specify a editDropdownOptionsArray option. This is normally used by the built-in HTML select box option, but we’ll re-purpose it for our custom dropdown. The values are simply the different genders we can choose.

You’re also not limited to a basic array of strings; your dropdown options could be complex objects as well:

editDropdownOptionsArray: [
  { id: 0, value: 'male' },
  { id: 1, value: 'female' },
  { id: 2, value: 'other }
]

You’d just need to make sure you bind to the right property to display the value.

Row Height

You’ll also notice that we set a custom rowHeight. This lets the dropdown widget fit nicely in our row without overlapping any of the borders. You could always resize it with CSS if you wanted to but this serves our purposes.

2. Edit Template

This is our editable cell template. We are using the “selectize” theme. UI-Select offers others but this one is nice and clean.

<ui-select ng-model="MODEL_COL_FIELD" theme="selectize"
    append-to-body="true">

  <ui-select-match placeholder="Choose...">{{ COL_FIELD }}</ui-select-match>
  <ui-select-choices repeat="item in col.colDef.editDropdownOptionsArray | filter: $select.search">
    <span>{{ item }}</span>
  </ui-select-choices>
</ui-select>

Note the append-to-body="true". That’s the trick to making the dropdown display right.

You’ll also see that we use two placeholders in our template: MODEL_COL_FIELD and COL_FIELD. The first one is used to bind the dropdown to the right property in our row entity.

The second is used to display the initial selected value in the dropdown when we double-click to edit.

We access the dropdown options we defined above by using the column’s column definition: col.colDef.editDropdownOptionsArray.

3. Wrap the Template

This template would work, but it’s got one problem: it won’t disappear when we click elsewhere on the page. The dropdown doesn’t know it’s supposed to hide when we want to interact with other elements.

We can fix this by wrapping the UI-Select directive in a custom directive where we’ll add some event bindings. We’ll call the directive ui-select-wrap and it will look a little something like this:

uiSelectWrap.$inject = ['$document', 'uiGridEditConstants'];
function uiSelectWrap($document, uiGridEditConstants) {
  return function link($scope, $elm, $attr) {
    $document.on('click', docClick);
    
    function docClick(evt) {
      if ($(evt.target).closest('.ui-select-container').size() === 0) {
        $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
        $document.off('click', docClick);
      }
    }
  };
}

What we’re looking for here is catching any clicks on the entire document that don’t fall without the container that holds our active UI-Select element.  We do this by attaching a click handler to $document (which is just an Angular wrapper around the standard document object).

We use jQuery’s closest() method to see if the click target has a .ui-select-container ancestor. If we didn’t want jQuery as a dependency we could also do this using the gridUtil service’s closestElm method like so: gridUtil.closestElm(evt.target, '.ui-select-container');

If the click isn’t on the UI-Select widget we fire END_CELL_EDIT and detach our click handler. Done!

Example

Here’s a plunker demonstrating the code we wrote above. You can double-click on the gender fields to show the dropdown.

What About Other Widgets?

Like we said above, there’s many many other dropdown widgets out there.  Just to name a few:

There may be ways to get these working but you have to ask the question: “Will these attach to the document body?” If not, you’ll be fighting with getting the dropdown to display outside the grid and that’s no fun.

If you have any questions about getting UI-Select to work in your grid, or if you want to explore using other widgets in UI-Grid, leave me a message in the comments. I respond to all of them!




Sign Up

Like what you read? Sign up for more! I love sharing tips, tricks, and methods to make web development faster and easier. And believe me, I hate spam just as much as you do.

The post UI-Grid and Dropdowns appeared first on Brian Hann.

]]>
https://brianhann.com/ui-grid-and-dropdowns/feed/ 114 287
Customize UI-Grid with Dynamic Cell Classes https://brianhann.com/customize-ui-grid-with-dynamic-cell-classes/ https://brianhann.com/customize-ui-grid-with-dynamic-cell-classes/#comments Sun, 03 May 2015 20:47:13 +0000 https://brianhann.com/?p=340 In 6 Ways to Take Control of Displayed Data in UI Grid we looked at some common methods you can use to control how your data looks in UI-Grid. A grid is only as good as the control it gives over the data displayed in it, and the goal of UI-Grid is definitely to give you as much […]

The post Customize UI-Grid with Dynamic Cell Classes appeared first on Brian Hann.

]]>
In 6 Ways to Take Control of Displayed Data in UI Grid we looked at some common methods you can use to control how your data looks in UI-Grid. A grid is only as good as the control it gives over the data displayed in it, and the goal of UI-Grid is definitely to give you as much control as possible.

In this post we are going to look at a couple more tricks that will get you closer to the goal of perfect customization

Dynamic Cell Classes

When you specify your grid’s columnDefs you can give each column a cellClass. This can either be a string, or in the case we’re going to look at: a function.  The function will receive the grid, row, column, and row and column indexes as arguments. You just return the appropriate class name

Here’s an example:

$scope.gridOptions = {
  enableSorting: true,
  columnDefs: [
    { field: 'name' },
    { field: 'gender',
      cellClass: function(grid, row, col, rowRenderIndex, colRenderIndex) {
        if (grid.getCellValue(row ,col).toLowerCase() === 'male') {
          return 'red';
        }
      }
    }
  ]
};

You don’t have to worry about figuring out your binding if you have a complicated one. The Grid provides a getCellValue() function. If you pass the row and column in it will return the value for that row+col combination.

Be aware that each grid cell has its background color set with a class definition that looks like this: .ui-grid-row:nth-child(odd) .ui-grid-cell. If you give your grid a custom class, like .grid you can just define your custom cell class as .grid .ui-grid-row .ui-grid-cell which will raise its CSS specificity enough to take over.

Lastly here’s a plunker that demonstrates what this looks like:




Want even more control over UI-Grid?

There's a new video series coming out tell you how to write plugins with the UI-Grid Plugin Generator. Sign up and be notified when the first video is out!

The post Customize UI-Grid with Dynamic Cell Classes appeared first on Brian Hann.

]]>
https://brianhann.com/customize-ui-grid-with-dynamic-cell-classes/feed/ 14 340
Pass Data to a UI-Bootstrap Modal Without $scope https://brianhann.com/pass-data-to-a-ui-bootstrap-modal-without-scope/ https://brianhann.com/pass-data-to-a-ui-bootstrap-modal-without-scope/#comments Thu, 23 Apr 2015 12:07:24 +0000 https://brianhann.com/?p=323 Angular UI Bootstrap‘s $modal service has a $scope argument that lets you assign it whatever $scope you want, so if you wanted your modal’s controller to use the same $scope as your parent controller you could do that. But what if you don’t want to use $scope? What if you’re using controllerAs and you want […]

The post Pass Data to a UI-Bootstrap Modal Without $scope appeared first on Brian Hann.

]]>
Angular UI Bootstrap‘s $modal service has a $scope argument that lets you assign it whatever $scope you want, so if you wanted your modal’s controller to use the same $scope as your parent controller you could do that.

But what if you don’t want to use $scope? What if you’re using controllerAs and you want to keep your code all scope-less, neat, and tidy?

Luckily there’s an alternative. You can send your data to the modal as resolves. Resolves first appeared with ngRoute, as a way to make sure information was available to a route’s controller before the view was initiated. It prevents elements shifting around in your view as data would otherwise  be loaded after the view shows up. It also keeps common data-loading tasks out of your controller.

Let’s look at an example. You’ve got a list of people in a table. Each has a name, and a delete button. When the user clicks the delete button a modal will come up confirming the delete.

We’ll pass the list of people and the person to delete into the modal’s controller using the resolve argument. Each property in the resolve object can be a string or function. If it’s a string, Angular will look for a service with that name and inject it into the modal controller. If it’s a function you can either return the value directly or return a promise if you’re fetching the data asynchronously.

Main.$inject = ['$modal'];
function Main($modal) {
  var vm = this;
  
  vm.deleteModal = deleteModal;
  vm.people = [
    'Fred',
    'Jim',
    'Bob'
  ];
  
  function deleteModal(person) {
    $modal.open({
      templateUrl: 'modal.html',
      controller: ['$modalInstance', 'people', 'person', DeleteModalCtrl],
      controllerAs: 'vm',
      resolve: {
        people: function () { return vm.people },
        person: function() { return person; }
      }
    });
  }

And just for clarity here’s our view, with the people ng-repeated in a table:

<table class="table">
  <tr ng-repeat="p in vm.people">
    <td>{{ p }}</td>
    <td>
      <button type="button" class="btn btn-sm btn-primary" ng-click="vm.deleteModal(p)">Delete</button>
    </td>
  </tr>
</table>

Alright, there’s our people and our person; person is passed in to the deleteModal function when the user clicks a delete button, deleteModal in turn passes people and person to DeleteModalCtrl as injectables.

Our modal controller can look like this:

function DeleteModalCtrl($modalInstance, people, person) {
  var vm = this;
  
  vm.person = person;
  vm.deletePerson = deletePerson;
  
  function deletePerson() {
    people.splice(people.indexOf(person), 1);
    $modalInstance.close();
  }
}

The injectables get passed in as arguments and we don’t need to use $scope at all, hooray! Finally, here is an example plunker showing how this all works together:




Sign Up

Like what you read? I'd love to send you more helpful tips and information, and believe me, I hate spam way more than you do!

The post Pass Data to a UI-Bootstrap Modal Without $scope appeared first on Brian Hann.

]]>
https://brianhann.com/pass-data-to-a-ui-bootstrap-modal-without-scope/feed/ 22 323
Write Your Own UI-Grid Plugin https://brianhann.com/write-your-own-ui-grid-plugin/ https://brianhann.com/write-your-own-ui-grid-plugin/#comments Tue, 21 Apr 2015 21:45:40 +0000 https://brianhann.com/?p=299 There are a lot of third-party modules out there for Angular, and even more plugins and extensions for those modules. But even with such a great ecosystem, you won’t always find the functionality that you need. If you’ve done web development for any length of time I’m sure you’ve had to write your own extensions, or […]

The post Write Your Own UI-Grid Plugin appeared first on Brian Hann.

]]>
There are a lot of third-party modules out there for Angular, and even more plugins and extensions for those modules.

But even with such a great ecosystem, you won’t always find the functionality that you need.

If you’ve done web development for any length of time I’m sure you’ve had to write your own extensions, or modify someone’s module to fit your needs. Writing those things from scratch can be a real bear, much less adding things like tests or a reliable build system.

And what if you want to share your finished product with others? It’s probably twice as much work if you want nice, clean documentation and usable demos.

I wanted to lower the barrier of entry for you and get you on your way to writing any UI-Grid plugin you want with a minimum of fuss. I would bet that you (yes you) have some great ideas. Why should you have to spend a ton of time building all the little non-plugin parts around your plugin before you actually build and share your plugin?

Answer: you don’t have to.

UI-Grid Plugin Generator

Meet the UI-Grid Plugin Generator: a simple Yeoman generator that you can use to create the base of your plugin. What’s it got? I’m glad you asked!

  • Runs on Gulp.
  • Complete build tool chain with js and less contatenation, minification, sourcemaps, and banners.
  • Angular-safe minification with ngAnnotate.
  • Keeps your code clean with JSHint and JSCS.
  • Automated testing with Karma.
  • Bundles your html templates with ngHtml2js.
  • Automated documentation and demo creation with Dgeni.

The last one is a killer. Don’t worry about building your plugin’s website from scratch. Just document your code, write a few tutorials, and Dgeni will generate a complete fully-functioning site that’s similar to UI-Grid’s. This has saved the UI-Grid team a ton of time, and is used in major projects like Angular itself, Ionic, and Angular Materials.

What about Feature X?

This project is brand new, so there’s a few things missing that will be added soon. The main hangup is Protractor. There is no e2e testing yet as it’s not working, but it will be fixed soon.

A couple other features on the roadmap are:

How Do I Get Started?

Here’s all the steps:

  1. Install the Yeoman generator (and Yeoman if you don’t have it already)
  2. Generate your plugin boilerplate.

Two steps, not bad right? Here’s what it looks like:

npm install -g yeoman generator-ui-grid-plugin
mkdir my-plugin && cd my-plugin
yo ui-grid-plugin

The generator will prompt you for your plugin name and some other information.

You can provide a Google Analytics tracking code if you want it embedded on your plugin’s site.

How Do I Publish?

The application is set up so that your Travis-CI takes your successful builds and creates a documentation & demo website with GitHub pages. Follow these here steps:

  1. Sign up for Travis: follow steps 1 and 2 here.
  2. Create a GitHub token for Travis to use. You can do that on your GitHub settings page. The token should only need access to your public repos. Make sure to copy your token and keep it safe. It can only be seen once so if you lose it you’ll have to generate a new one.
  3. Encrypt your token with the travis Ruby gem. There’s more detailed instructions on the project’s homepage.

Customization Ideas

Here are some ideas:

  • Use SASS instead of less for CSS compilation.
  • Plug in a bootstrap theme to make the site look more individual. You might look: here, here, or here.
  • Mash-up your plugin with other cool Angular modules, like UI Tree or ngStorage.
  • Tinker with Dgeni to add more advanced docs generation.

If you want to watch a tutorial video series on creating a fully-functioning plugin, sign up below and you’ll receive a link when it’s ready.




I'm interested!

Send me a link to the video tutorials when they're done.

The post Write Your Own UI-Grid Plugin appeared first on Brian Hann.

]]>
https://brianhann.com/write-your-own-ui-grid-plugin/feed/ 3 299
UI-Grid: The Easiest Customization You’ll Ever Write https://brianhann.com/ui-grid-the-easiest-customization-youll-ever-write/ https://brianhann.com/ui-grid-the-easiest-customization-youll-ever-write/#comments Tue, 07 Apr 2015 21:07:22 +0000 https://brianhann.com/?p=208 Currently UI-Grid doesn’t automatically show any messages when data is loading. Nor does it show anything when there’s no data to show. This can be a bit confusing for your end users as all they’ll see is an empty white space in the grid. Luckily this is easy to fix with the most misunderstood feature […]

The post UI-Grid: The Easiest Customization You’ll Ever Write appeared first on Brian Hann.

]]>
Currently UI-Grid doesn’t automatically show any messages when data is loading.

Nor does it show anything when there’s no data to show.

This can be a bit confusing for your end users as all they’ll see is an empty white space in the grid.

Luckily this is easy to fix with the most misunderstood feature of Angular 1.2: transclusion.

Bonus: Want to see the advanced use cases in the image, like selecting data by clicking on a map and loading data from a file the user drags and drops on to the grid? Enter your email and receive a link to a special resource

Angular is a Donut

Angular transclusion is a jelly donut

Transclusion is delicious.

Think of transclusion as a delicious jelly donut. UI-Grid is the donut and the functionality you want to add is the jelly. Is your functionality raspberry flavored? Never mind; don’t answer that.

In addition to simply being inside the donut and available for consumption, your jelly filling has access to all the features and properties of the donut. Like the frosting or sprinkles, if there are any. Alright so maybe the metaphor breaks down there. The point is that your transcluded will have a scope that is a descended of your grid’s scope, so you can access the grid property and all of its settings and data in your expressions.

What does transclusion look like? Not all that much:

<div ui-grid="gridOptions" class="grid">
  <div>I'm a special little transcluded div for grid {{ grid.id }}!</div>
</div>

The div inside your ui-grid element is what’s being transcluded. Pretty simple, right?

Alright enough theory, let’s get started. You and I are going to look at two use cases:

  1. A “Loading…” message with a spinner indicator
  2. A “No Data” message

Loading Message

This message will be an overlay that will sit on top of the grid while your data is loading. The markup is short and sweet, just a couple divs and your message. We will use an ng-hide on the overlay that will only let it show if we are loading and have no data.

<div ui-grid="gridOptions" class="grid">
  <div class="no-data" ng-hide="gridOptions.data.length && !loading">
    <div class="msg">
      <span>Loading Data...</span>
    </div>
  </div>
</div>

Our controller code is pretty short as well. We’ll make an init function that is called when the controller starts up. That will set a loading flag on the scope, and then unset it in our data fetch’s finally handler.

app.controller('MainCtrl', function ($scope, $http) {
  $scope.gridOptions = {};

  init();

  function init() {
    $scope.loading = true;

    $http.get('names.json')
      .success(function (data) {
        $scope.gridOptions.data = data;
      })
      .finally(function () { $scope.loading = false; })
  }
});

Alright, if we fire this up what are we going to see?

ui-grid loading message image 1

This is a non-interactive image of what the user would see.

Hmm, that’s not quite right. It looks like any content we transclude will just be appended to the grid element’s contents. I guess we’ll need some custom CSS to position it correctly and make it look nice. We want the overlay to:

  1. Overlay the entire grid
  2. Darken the grid a bit with some opacity
  3. Generally look nice
  4. Have an animated progress widget

Here’s the CSS for all that. Read the comments for what it all does.

/*
   This is the background of our overlay. We need it to be
   absolutely positioned within the grid, and fill from
   top to bottom, and the full width. It will also have
   a black background with 40% opacity.
*/
.grid-msg-overlay {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 100%;
  background: rgba(0, 0, 0, 0.4);
}

/*
  This guy will contain our message. We want it centered
  so it's positioned absolutely with percentage-based
  offsets and dimensions. It also has some basic border
  stuff and most important is using "display: table" so
  we can vertically center its contents.
*/
.grid-msg-overlay .msg {
  opacity: 1;
  position: absolute;
  top: 20%;
  left: 20%;
  width: 60%;
  height: 50%;
  background-color: #eee;
  border-radius: 4px;
  border: 1px solid #555;
  text-align: center;
  font-size: 24px;
  display: table;
}

/*
  Lastly this is the actual message text. It uses
  display: table-cell so the vertical alignment
  works properly.
*/
.grid-msg-overlay .msg span {
  display: table-cell;
  vertical-align: middle;
}

Note on Vertical Centering: This is beyond the scope of this post, but there’s a bunch of different ways to vertically center elements. I’m using table CSS here, which might not be the best for you. Check out this Chris Coyier post for more.

Our result is much better! Let’s take a look at a demo of what we’ve just done:

OK, looking good. But what if nothing loads and our users are still staring at an empty grid? Since we’ve got Let’s add a “No Data” message.

No Data, No Problem

We already have the CSS set up so all we need is some markup:

<div ui-grid="gridOptions" class="grid">
  <div class="no-data" ng-hide="gridOptions.data.length && !loading">
    <div class="msg">
      <span>Loading Data...</span>
    </div>
  </div>
  <div class="no-data" ng-hide="!gridOptions.data.length && loadAttempted">
    <div class="msg">
      <span>No Data</span>
    </div>
  </div>
</div>

Then we can add the loadAttempted flag to the finally handler:

.finally(function () {
  $scope.loading = false;
  $scope.loadAttempted = true;
});

Here’s our resulting plunker:

Et voila! We are done.  Transclusion is great for when you just want some simple extensions. Would you like more examples, such as:

  • Selecting data for UI-Grid by selecting from a map of the US
  • Loading message while using external paging
  • Drag-and-drop file input for loading custom JSON data into grid

Enter your email below and I’ll send you a link to our bonus area where you can these uses cases for transclusion in UI-Grid, with example demos+code and explanatory documentation. Enjoy!




Sign up

Subscribe to receive access to the bonus resource: Advanced UI-Grid Customization with Transclusion

The post UI-Grid: The Easiest Customization You’ll Ever Write appeared first on Brian Hann.

]]>
https://brianhann.com/ui-grid-the-easiest-customization-youll-ever-write/feed/ 14 208
Create a Modal Row Editor for UI-Grid in Minutes https://brianhann.com/create-a-modal-row-editor-for-ui-grid-in-minutes/ https://brianhann.com/create-a-modal-row-editor-for-ui-grid-in-minutes/#comments Sat, 28 Mar 2015 21:20:16 +0000 https://brianhann.com/?p=162 UI-Grid has editing features built in. But sometimes you want to edit data all at once. And add some custom validation, third-party controls, and so on. Normally if you wanted to create your own row modal editor it would mean wiring up a modal service like the one from UI Bootstrap, writing your own form out by […]

The post Create a Modal Row Editor for UI-Grid in Minutes appeared first on Brian Hann.

]]>
UI-Grid has editing features built in.

But sometimes you want to edit data all at once.

And add some custom validation, third-party controls, and so on.

Normally if you wanted to create your own row modal editor it would mean wiring up a modal service like the one from UI Bootstrap, writing your own form out by hand, including the validation and controls.

It can take a lot of time to write up a form for all your little data points, and let’s be honest, it’s pretty boring. There’s only so many times I can type in <input type="text" /> (even with an HTML generator) before I’m sighing resignedly and shifting in my seat.

In this post I’m going to show you how to short-circuit this routine and create a row editor for UI-Grid in just minutes with a great module called Angular Schema Form. You won’t have to write up any form controls, validation, or save/cancel behavior. The only thing you have to supply is the configuration, and Angular Schema Form will take care of the rest.

Making Your Rows Editable

The first thing we need to do is some basic hook-ups to open a modal to edit our rows. To do this we’ll add a little button as the first column of our grid. For the modal we’ll use UI Bootstrap’s $modal service, and Font Awesome for icons.

These are our column defs, we’ll use edit-button.html as our custom button template.

columnDefs: [
  { field: 'id', name: '', cellTemplate: 'edit-button.html', width: 34 },
  { name: 'name' },
  { name: 'company' },
  { name: 'phone' },
  { name: 'City', field: 'address.city' },
]

And here’s the cell template:

<div class="ui-grid-cell-contents">
  <button type="button"
     class="btn btn-xs btn-primary"
     ng-click="grid.appScope.vm.editRow(grid, row)">
    <i class="fa fa-edit"></i>
  </button>
</div>

Notice that the ng-click is firing off a function bound to something called grid.appScope. UI-Grid uses isolate scope, which means that it does not inherit from the scope of its ancestor elements; so if you stick your grid inside an element that has a controller on it, you can’t just access your controller’s scope directly. To help you, UI-Grid binds its parent scope to grid.appScope. To give you an idea here’s what part of our controller would look like:

MainCtrl.$inject = ['$modal'];
function MainCtrl($modal) {
  var vm = this;

  vm.editRow = editRow;
  vm.gridOptions = {
    columnDefs: [
      { field: 'id', name: '', cellTemplate: 'edit-button.html', width: 34 },
      { name: 'name' },
      { name: 'company' },
      { name: 'phone' },
      { name: 'City', field: 'address.city' },
    ]
  };
  
  init();

  //////////// 
  
  function editRow(grid, row) {
    $modal.open({
      templateUrl: 'edit-modal.html',
      controller: ['$modalInstance', 'grid', 'row', RowEditCtrl],
      controllerAs: 'vm',
      resolve: {
        grid: function () { return grid; },
        row: function () { return row; }
      }
    });
  }
  
  function init() {
    $http.get('http://ui-grid.info/data/500_complex.json')
      .success(function (data) {
        vm.gridOptions.data = data;
      });
  }
}

There’s our editRow function, which we expose by binding it to our controller. If we declare our controller like ng-controller="MainCtrl as vm" then we’ll have the vm property available on grid.appScope to use in our button template. RowEditCtrl will be the controller for the modal which we’ll define later. The editRow function will take the grid and row as arguments and we’ll inject them into RowEditCtrl as locals with resolve. You can read about all this in the UI Bootstrap modal docs.

The Modal

The modal is just going to be a super simple bootstrap template, with a form and save and cancel buttons:

<div>
  <div class="modal-header">
      <h3 class="modal-title">Edit Row</h3>
  </div>
  <div class="modal-body">
    <form sf-schema="vm.schema" sf-form="vm.form" sf-model="vm.entity"></form>
  </div>
  <div class="modal-footer">
      <button class="btn btn-success" ng-click="vm.save()">Save</button>
      <button class="btn btn-warning" ng-click="$close()">Cancel</button>
  </div>
</div>

What’s that sf-schema stuff? I’m glad you asked!

Angular Schema Form

Angular Schema Form is a directive that takes a few options and generates a form with inputs bound to the data that you specify.

First there’s the schema. This is a JSON Schema standard object that defines the data in your entity: their type (string, number, boolean, array, etc), title, description, and so on.

Defining Our Schema

Since we know what columns on grid is going to have, we can define our schema in a constant that we can inject in our app anywhere we want:

.constant('PersonSchema', {
  type: 'object',
  properties: {
    name: { type: 'string', title: 'Name' },
    company: { type: 'string', title: 'Company' },
    phone: { type: 'string', title: 'Phone' },
    'address.city': { type: 'string', title: 'City' }
  }
})

Notice the type: 'object' and properties that we use to set up the root of our schema. Don’t forget this or your form will not work with no errors and you’ll be scratching your head like I did.

All our elements are pretty straight-forward. The only exception is the city, which is a slightly more complex binding. Angular Schema Form uses objectpath library to determine bindings so there’s some options if you have nested properties you want to access.

Form Setup

Remember those sf- attributes on our form in the modal template? Well the first one, sf-schema, gets bound to the schema we just created. sf-model is bound to the object we want to edit, in this case our row, but if we bind directly to the row we will be updating it live, which can be nice sometimes but it would be good to have a “cancel” feature. Let’s do that.

function RowEditCtrl($modalInstance, PersonSchema, grid, row) {
  var vm = this;
  
  vm.schema = PersonSchema;
  vm.entity = angular.copy(row.entity);
  vm.form = [
    'name',
    'company',
    'phone',
    'address.city'
  ];
  
  vm.grid = grid;
  vm.row = row;
  vm.save = save;
  
  function save() {
    // Copy row values over
    row.entity = angular.extend(row.entity, vm.entity);
    $modalInstance.close(row.entity);
  }
}

Alright, our modal controller is going to get $modalInstance, which the $modal service injects for us. We can use that to interact with the modal (closing it and so on). We’ll provide the PersonSchema, copy our row entity so we’re not directly editing the row, and then the next property is form. What’s that?

We’ve given our form a schema, which tells it what properties to expect, but how about what order they come in, or which ones we want to edit in this form?  If we didn’t really care we could do vm.form = ['*'], and then in our &lt;form&gt; do sf-form="vm.form". The ['*'] tells Angular Schema Form to just use all the properties from the schema, in whatever order the come out in. The problem with this is our address.city property won’t show up correctly. We have to explicitly tell the form what to expect, and since we have to tell it that we have to tell it all of them.

There’s lots of options for defining your form with Angular Schema Form: you can specify input types (text, radio, select, button groups), do hierarchical lists, tabs, conditional visibility, etc. There’s good examples on the project site.

The only thing left is the save function which copies over our updated row entity and closes the modal. Tada! You are the proud owner of a modal editor for UI-Grid which you can update whenever you want with only a couple lines. Almost painless, right?

Demo

Here’s a plunker showing it all working together.

Note: plnkr.co has been a little wonky about not loading fully sometimes; you may have to refresh it.

Et Fin

I hope you enjoyed this guide, and I’d like to help you out some more!  Next time we’ll walk through creating a UI-Grid plugin that will let us reuse this modal row editor code and create them wherever we want in just seconds. We’ll talk about Angular development best practices and use a plugin skeleton for creating tests, automatically generating documentation, and all that fun stuff.




Sign Up

Sign up here and I'll send you more tips, tricks, and shortcuts like this. I hate spam MORE than you and promise to only send things that will make your life better.

The post Create a Modal Row Editor for UI-Grid in Minutes appeared first on Brian Hann.

]]>
https://brianhann.com/create-a-modal-row-editor-for-ui-grid-in-minutes/feed/ 30 162
Atlassian Stash Error: Missing configuration path for module section https://brianhann.com/atlassian-stash-error-missing-configuration-path-for-module-section/ https://brianhann.com/atlassian-stash-error-missing-configuration-path-for-module-section/#respond Mon, 23 Mar 2015 23:43:48 +0000 https://brianhann.com/?p=114 I came across this rather inscrutable Atlassian Stash error after I had made changes to my submodules in git. If you run across this error it may be because you’ve manually edited your .gitmodules file: An error occurred while executing an external process: Missing configuration path for module section: <module name> The submodule repository itself will […]

The post Atlassian Stash Error: Missing configuration path for module section appeared first on Brian Hann.

]]>
I came across this rather inscrutable Atlassian Stash error after I had made changes to my submodules in git. If you run across this error it may be because you’ve manually edited your .gitmodules file:

An error occurred while executing an external process: Missing configuration path for module section: <module name>

The submodule repository itself will come up fine, but the parent repository will be completely inaccessible. In my particular case I had somehow managed to fat-finger my.gitmodules to look like this:

[submodule "submodule-name"]
  path = submodule-name
  url = http://url.to.my.repo.git

[submodules "submodule-name"]
  master = master

Simply removing the last two lines fixed the issue; “submodules” isn’t even a valid entry in .gitmodules. Good luck!




Sign Up

Like what you read? Sign up for more! I love sharing tips, tricks, and methods to make web development faster and easier. And believe me, I hate spam just as much as you do.

The post Atlassian Stash Error: Missing configuration path for module section appeared first on Brian Hann.

]]>
https://brianhann.com/atlassian-stash-error-missing-configuration-path-for-module-section/feed/ 0 114
6 Ways to Take Control of Displayed Data in UI Grid https://brianhann.com/6-ways-to-take-control-of-how-your-ui-grid-data-is-displayed/ https://brianhann.com/6-ways-to-take-control-of-how-your-ui-grid-data-is-displayed/#comments Mon, 16 Mar 2015 20:09:15 +0000 http://104.131.219.158/?p=71 Getting your data displayed just right in UI Grid can be a huge pain. You might just want to format some numbers, or you might want to embed something complex like a chart or custom directive. In this post outline six (plus one) different methods you can use to get your data just the way […]

The post 6 Ways to Take Control of Displayed Data in UI Grid appeared first on Brian Hann.

]]>
Getting your data displayed just right in UI Grid can be a huge pain.

You might just want to format some numbers, or you might want to embed something complex like a chart or custom directive.

In this post outline six (plus one) different methods you can use to get your data just the way you want it:

  • Bindings
  • Update: Cell Classes
  • Cell Filters
  • Cell Templates
  • Links
  • Buttons
  • Custom directives

Bindings

UI Grid columns can be bound to any sort of Angular expression, as well as property names that would normally break an expression:

var columnDefs = [
 { field: 'name' },
 { field: 'addresses[0][0][1]' },

 /*
 Yes, you can use hyphens, plus signs, etc.

 Normally something like {{ row.entity.first-name }} would
 bomb but UI Grid pre-processes your bindings to make sure they work correctly.
 */
 { field: 'first-name' },

 /* Function expressions work too */
 { field: 'getCurrency()' },
 { field: 'transformValue(row.entity.myField)' },
];

Cell Classes

When you specify your grid’s columnDefs you can specify a CSS class for that column with the cellClass property. The simplest type is a just a string representing the CSS class name, though you can also specify a function if you want dynamic control.

var columnDefs = [
 { field: 'name' },
 { field: 'address', cellClass: 'address' }
];

Cell Filters

Cell filters can transform the displayed value for a column while leaving the model intact. In this plunker the amount column contains floating point values, but we only want to display the integer part. A simple cellFilter that uses .toFixed() will alter the displayed value the way we want.

Passing Arguments

In the third column I am using two different fields. The column itself is bound to the same field as the second column, but on the filter I am passing the scope like so: cellFilter: 'currencyFilter:this'. Using the this argument on your filter will pass the scope.

Then I can lookup the currency symbol I want using the currency field from the row.

If you double-click a cell in the Currency column you’ll see that the raw value is still available to be edited. Cell filters only change your displayed value!

Cell Templates

Column definitions take a cellTemplate argument you can use to give your cells a custom template. You can specify it a few different ways, with a url (relative or absolute), an Angular template ID, a string/Angular element, or a promise.

var columnDefs = [
  { field: 'name', cellTemplate: 'name-template.html' },
  { field: 'name', cellTemplate: 'myTemplateId' },
  { field: 'name', cellTemplate: $.get('url-to-your-template.html') }
];

Bindings in Cell Templates

There are a couple options for your binding your row’s data in your template. You can access the row object which is in the local scope; row.entity will contain the reference to your object, so if you want the “name” field, you can bind like so: {{ row.entity.name }}.

If you’ve already defined your binding in your column definition you can use one of UI Grid’s placeholders: {{ COL_FIELD }}. UI Grid will automatically replace COL_FIELD with the appropriate binding. If you need to two-way bind to your row, like when you’re using the edit feature, you’ll need to use the MODEL_COL_FIELD placeholder.

var columnDefs = [
  {
    field: 'image_url',
    cellTemplate: '<div class="ui-grid-cell-contents"><img src="{{ COL_FIELD }}" /></div>'
  }
];

Links and Buttons

Links are simple, just use regular old HTML. Wrapping your cell in an element using the class .ui-grid-cell-contents will apply the proper CSS settings like padding, overflow, etc., and make sure your cell fits in its space nicely.

var columnDefs = [
  {
    field: 'email',
    cellTemplate: '<div class="ui-grid-cell-contents"><a href="mailto:{{ COL_FIELD }}">Send E-Mail</a></div>'
  }
];

In the plunker below I’m using a basic cellTemplate to bind both the first and last name fields into one column. On the second column I’m using a simple link like above (note: none of these emails are real).

On the third one, I’m using a template that’s referenced in index.html. If you check the bottom of that file you’ll see the template inside a &lt;script type="text/ng-template"&gt; tag. Any tags of that type will put automatically put into the template cache ($templateCache) by Angular, with the id you specify.

Also in the third column template you can see I’m using a variable called grid.appScope. Anywhere in your templates, grid.appScope will be bound to the parent scope of your grid. I use it to bind my button to a function in my controller that will open a new window with a url to google maps with the address in it (note: most if not all of these address will not exist).

Tooltips

Tooltips like those from UI Bootstrap are a little bit different because they float over your content. By default the grid’s cells are set to hide any overflow, so if you just pop a tooltip in there it won’t show up and you’ll probably be very confused. You can solve this by either adding some custom overflow CSS to your cellTemplate, or if you’re using UI Bootstrap you can add tooltip-append-to-body="true" to the element with your tooltip and the tooltip will be appended to the body and absolutely positioned where it needs to be.

Hover over the names in the plunker below to see tooltips with overflow CSS; the third column uses the append-to-body method:

Tooltips cannot overflow beyond the constraints of the viewport, so they won’t show above the first row or below the bottom row, for example.

Custom Directives

You can put absolutely anything in your cell templates, just remember to use .ui-grid-cell-contents if you’re not applying your own custom cell CSS.

Here I’ve used d3.js, nvd3.js, and angular-nvd3.js to create sparkline charts in my cells.

Summin’ it up

You should be able to do the vast majority of what you want with your data using these six methods. If you have questions, suggestions, or just want to say hi, drop in to our gitter channel we’d love to meet you!

If you want some help navigating through these different customization methods, enter your email below to get a flowchart PDF showing you which method to choose for a given need.




Free UI-Grid Flowchart

The flow chart Which UI-Grid Customization Should I Choose? will help you pick the customization method best suited for your situation.

Enter your email address and you'll receive a link to the Bonus Area with the flowchart PDF and other helpful resources.


The post 6 Ways to Take Control of Displayed Data in UI Grid appeared first on Brian Hann.

]]>
https://brianhann.com/6-ways-to-take-control-of-how-your-ui-grid-data-is-displayed/feed/ 85 71