分类目录归档:前端

前端包括Javascript和css等

angularjs directive

在AngularJS中,Dom操作应该在directive里进行,而不应该在controllers, services或者其他任何地方。

directives命名

  • 使用不重复的前缀
    • 防止和其他人重复
    • 易读
  • 不要用ng-作为你的directive的前缀
  • 常见情况:两个单词
    • AngularUI project 用的是 “ui-“

什么时候用directives?

  • 可复用的HTML控件
    <my-widget>

  • 可复用的HTML行为
    <div ng-click="...">

  • 包装一个jQuery插件
    <div ui-date></div>

  • 你需要和DOM交互的绝大多数情况

创建一个directive

先给你的directive创建一个module

angular.module('MyDirectives',[]);

在你的app里加入directive module的依赖

angular.module('app',['MyDirectives']);

注册你的directive

angular.module('MyDirectives')
.directive('myDirective', function(){
  // TODO:
});

在HTML里使用你的directive

<div my-directive>...</div>

Restrict参数

‘A’

attributes
<div my-directive></div>

‘E’

element
<my-directive></my-directive

‘C’ ‘M’

rarely used
‘C’: classes ‘M’ : comments

组合

restrict: 'AE'

隔离的scope

directive默认是没有scope的,我们可以给它一个scope

scope: {
  someParam: '='
}

或者
scope: true
这个scope不会继承其他scope

scope参数

scope参数是从HTML的属性(attribute)传进来的

scope: {
  param1: '=', // 双向绑定(directive和controller)
  param2: '@', // 单向绑定
  param3: '&'  // 单向行为
}
<div my-directive
  param1="someVariable"
  param2="My name is {{name}}"
  param3="doSth()"
>

$digest already in progress 解决办法

常见原因

除了简单的不正确调用 $apply$digest,有些情况下,即使没有犯错,也有可能得到这个错误。

可能出错的代码

function MyController($scope, thirdPartyComponent) {
  thirdPartyComponent.getData(function(someData) {
    $scope.$apply(function() {
      $scope.someData = someData;
    });
  });
}

改成这样就可以了

function MyController($scope, thirdPartyComponent) {
  thirdPartyComponent.getData(function(someData) {
    $timeout(function() {
      $scope.someData = someData;
    }, 0);
  });
}

使用AngularJS service作为KendoUI的datasource

angular.module("app", ["kendo.directives"])
.controller("news", function($scope,newsService) {
  var dataSource = new kendo.data.DataSource({
    transport: {
      read: dataSourceRead
    }
  });
  function dataSourceRead(options){
    //todo: show loading
    newsService.getByCategory($scope.selectedCategory.value)
      .then(
        function(response){
          options.success(response);
          //todo: hide loading
        },
        function(response){
          options.error(response);
          //todo: handle errors.
        });
  }
  $scope.newsListViewOptions = {
    dataSource: dataSource
  };
})
.service('newsService', function($q, $http) {
  this.getByCategory = function(category){
    var url = "your url";
    var request = $http({
      method: "jsonp",
      url: url
    });
    return( request.then( handleSuccess, handleError ) );
  };
  function handleError( response ) {
    //if no message return from server
    if (
      ! angular.isObject( response.data ) ||
      ! response.data.message
      ) {
      return( $q.reject( "An unknown error occurred." ) );
    }
    return( $q.reject( response.data.message ) );
  }
  function handleSuccess( response ) {
    return( response.data );
  }
});

angularjs service factory provider 区别

Services
Syntax: module.service( ‘serviceName’, function );
Result: When declaring serviceName as an injectable argument you will be provided with an instance of the function. In other words new FunctionYouPassedToService().
Factories
Syntax: module.factory( ‘factoryName’, function );
Result: When declaring factoryName as an injectable argument you will be provided with the value that is returned by invoking the function reference passed to module.factory.
Providers
Syntax: module.provider( ‘providerName’, function );
Result: When declaring providerName as an injectable argument you will be provided with ProviderFunction().$get(). The constructor function is instantiated before the $get method is called – ProviderFunction is the function reference passed to module.provider.

Ionic and Cordova's DeviceReady – My Solution

Folks know that I’ve been madly in love with the?Ionic framework?lately, but I’ve run into an issue that I’m having difficulty with. I thought I’d blog about the problem and demonstrate a solution that worked for me. To be clear, I think my solution is probably?wrong. It works, but it just doesn’t?feel?right. I’m specifically sharing this blog entry as a way to start the discussion and get some feedback. On the slim chance that what I’m showing?is?the best solution… um… yes… I planned that. I’m brilliant.

The Problem

So let’s begin by discussing the problem. Given a typical Ionic app, your Angular code will have a .run method that listens for the ionicPlatform’s ready event. Here is an example from the “base” starter app (https://github.com/driftyco/ionic-app-base/blob/master/www/js/app.js):
 

// Ionic Starter App
// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a attribute in index.html)
// the 2nd parameter is an array of 'requires'
angular.module('starter', ['ionic'])
.run(function($ionicPlatform) {
  $ionicPlatform.ready(function() {
    // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
    // for form inputs)
    if(window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
    }
    if(window.StatusBar) {
      // Set the statusbar to use the default style, tweak this to
      // remove the status bar on iOS or change it to use white instead of dark colors.
      StatusBar.styleDefault();
    }
  });
})

The ionicPlatform.ready event is called when Cordova’s deviceready event fires. When run on a desktop, it is fired when on window.load. Ok, so in my mind, this is where I’d put code that’s normally in a document.ready block. So far so good.
Now let’s imagine you want to use a plugin, perhaps the Device plugin. Imagine you want to simply copy a value to $scope so you can display it in a view. If that controller/view is the first view in your application, you end up with a race condition. Angular is going to display your view and fire off ionicPlatform.ready asynchronously. That isn’t a bug of course, but it raises the question. If you want to make use of Cordova plugin features, and your application depends on it immediately, how do you handle that easily?
One way would be to remove ng-app from the DOM and bootstrap Angular yourself. I’ve done that… once before and I see how it makes sense. But I didn’t want to use that solution this time as I wanted to keep using ionicPlatform.ready. I assumed (and I could be wrong!) that I couldn’t keep that and remove the ng-app bootstraping.
So what I did was to add an intermediate view to my application. A simple landing page. I modified the stateProvider to add a new state and then made it the default. In my ionicPlatform.ready, I use the location service to do a move to the previously default state.

.run(function($ionicPlatform,$location,$rootScope) {
  $ionicPlatform.ready(function() {
    // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
    // for form inputs)
    if(window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
    }
    if(window.StatusBar) {
      // org.apache.cordova.statusbar required
      StatusBar.styleDefault();
    }
	  $location.path('/tab/dash');
	  $rootScope.$apply();
  });
})

This seemed to do the trick. My controller code that’s run for the views after this was able to use Cordova plugins just fine. How about a real example?

The Demo

One of the more recent features to land in Ionic is?striped-style tabs. This is an Android-style tab UI and it will be applied automatically to apps running on Android. The difference is a bit subtle when the tabs are on the bottom:

But when moved to the top using tabs-top, it is a bit more dramatic.

Ok… cool. But I wondered – how can I get tabs on top?just?for Android? While I’m not one of those people who believe that UI elements have to be in a certain position on iOS versus Android, I was curious as to how I’d handle this programmatically.
Knowing that it was trivial to check the Device plugin, and having a way now to delay the view until my plugins were loaded, I decided to use the approach described above to ensure I could access the platform before that particular view loaded.
Here is the app.js file I used, modified from the tabs starter template.

// Ionic Starter App
// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a attribute in index.html)
// the 2nd parameter is an array of 'requires'
// 'starter.services' is found in services.js
// 'starter.controllers' is found in controllers.js
angular.module('starter', ['ionic', 'starter.controllers', 'starter.services'])
.run(function($ionicPlatform,$location,$rootScope) {
  $ionicPlatform.ready(function() {
    // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
    // for form inputs)
    if(window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
    }
    if(window.StatusBar) {
      // org.apache.cordova.statusbar required
      StatusBar.styleDefault();
    }
	  $location.path('/tab/dash');
	  $rootScope.$apply();
  });
})
.config(function($stateProvider, $urlRouterProvider) {
  // Ionic uses AngularUI Router which uses the concept of states
  // Learn more here: https://github.com/angular-ui/ui-router
  // Set up the various states which the app can be in.
  // Each state's controller can be found in controllers.js
  $stateProvider
    // setup an abstract state for the tabs directive
  	.state('home', {
		url:"/home",
		templateUrl:'templates/loading.html',
		controller:'HomeCtrl'
	})
    .state('tab', {
      url: "/tab",
      abstract: true,
      templateUrl: function() {
		  if(window.device.platform.toLowerCase().indexOf("android") >= 0) {
			  return "templates/tabs_android.html";
		  } else {
			  return "templates/tabs.html";
		  }
	  },
    })
    // Each tab has its own nav history stack:
    .state('tab.dash', {
      url: '/dash',
      views: {
        'tab-dash': {
          templateUrl: 'templates/tab-dash.html',
          controller: 'DashCtrl'
        }
      }
    })
    .state('tab.friends', {
      url: '/friends',
      views: {
        'tab-friends': {
          templateUrl: 'templates/tab-friends.html',
          controller: 'FriendsCtrl'
        }
      }
    })
    .state('tab.friend-detail', {
      url: '/friend/:friendId',
      views: {
        'tab-friends': {
          templateUrl: 'templates/friend-detail.html',
          controller: 'FriendDetailCtrl'
        }
      }
    })
    .state('tab.account', {
      url: '/account',
      views: {
        'tab-account': {
          templateUrl: 'templates/tab-account.html',
          controller: 'AccountCtrl'
        }
      }
    });
  // if none of the above states are matched, use this as the fallback
  $urlRouterProvider.otherwise('/home');
});

You can see where I use the location.path mechanism after the ionicPlatform.ready event has fired. You can also see where I sniff the device platform to determine which template to run. tabs_android.html is the exact same as tabs.html – but with the tabs-top class applied (*). The biggest drawback here is that the application would error when run on the desktop. That could be avoided by sniffing for the lack of window.device and just setting it to some default: window.device = {platform : "ios"};
So that’s it. What do you think? I have to imagine there is a nicer way of handling this. Maybe I’m being lazy but I want to use Ionic’s killer directives and UX stuff along with Cordova plugins and not have to use an awkward workaround like this.
* A quick footnote. I noticed that if I tried to add tabs-top to the ion-tabs directive, it never worked. For example, this is what I tried first:<ion-tabs ng-class="{'tabs-top':settings.isAndroid}"> I used code in my controller that always set it to true (I wasn’t worried about the device plugin yet) and it never actually updated the view. It’s like the controller scope couldn’t modify the view for some odd reason.

Ionic可折叠列表

html:

<html ng-app="ionicApp">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <title>Ionic collapsible list</title>
    <link href="//code.ionicframework.com/1.0.0-beta.12/css/ionic.css" rel="stylesheet">
    <script src="//code.ionicframework.com/1.0.0-beta.12/js/ionic.bundle.js"></script>
    </script>
  </head>
  <body ng-controller="MyCtrl">
    <ion-header-bar class="bar-positive">
      <h1 class="title">Ionic collapsible list</h1>
    </ion-header-bar>
    <ion-content>
      <ion-list>
        <div ng-repeat="group in groups">
          <ion-item class="item-stable"
                    ng-click="toggleGroup(group)"
                    ng-class="{active: isGroupShown(group)}">
              <i class="icon" ng-class="isGroupShown(group) ? 'ion-minus' : 'ion-plus'"></i>
            &nbsp;
            Group {{group.name}}
          </ion-item>
          <ion-item class="item-accordion"
                    ng-repeat="item in group.items"
                    ng-show="isGroupShown(group)">
            {{item}}
          </ion-item>
        </div>
      </ion-list>
    </ion-content>
  </body>
</html>

css

.list .item.item-accordion {
  line-height: 38px;
  padding-top: 0;
  padding-bottom: 0;
  transition: 0.09s all linear;
}
.list .item.item-accordion.ng-hide {
  line-height: 0px;
}
.list .item.item-accordion.ng-hide-add,
.list .item.item-accordion.ng-hide-remove {
  display: block !important;
}

js

angular.module('ionicApp', ['ionic'])
.controller('MyCtrl', function($scope) {
  $scope.groups = [];
  for (var i=0; i<10; i++) {
    $scope.groups[i] = {
      name: i,
      items: [],
      show: false
    };
    for (var j=0; j<3; j++) {
      $scope.groups[i].items.push(i + '-' + j);
    }
  }
  /*
   * if given group is the selected group, deselect it
   * else, select the given group
   */
  $scope.toggleGroup = function(group) {
    group.show = !group.show;
  };
  $scope.isGroupShown = function(group) {
    return group.show;
  };
});

codepen例子:

See the Pen Ionic collapsible list by Qing Sheng (@shengoo) on CodePen.

jsfiddle: