angularJS指令实战,制作一个可复用的幻灯组件(二)

上一篇文章介绍了ngSlide插件,接下来就该为大家介绍它是如何被制作出来的了。

指令

我们看到这个插件的基本用法就是

<div slider></div>

那么这里的slider是angularJs中的指令名,指令是用来做HTML扩展的,我在上一文的示例

<div slider>
	<li>
            <img src="1.jpg" alt=""/>
            <p>标题</p>
    </li>
	<li>
        <img src="1.jpg" alt=""/>
        <p>标题</p>
    </li>
	<li>
        <img src="1.jpg" alt=""/>
        <p>标题</p>
    </li>
</div>

中并没有写ul,但它实际上在浏览器里的运行效果却是这样的

<div class="cc" slider>
    <ul class="cc">
        <li>
            <img src="1.jpg" alt=""/>
            <p>标题</p>
        </li>
        <li>
            <img src="1.jpg" alt=""/>
            <p>标题</p>
        </li>
        <li>
            <img src="1.jpg" alt=""/>
            <p>标题</p>
        </li>
    </ul>
</div>

那么多出来的这些代码就是由angular的指令生成的了,创建一个js文件,命名为ngSlide.js,书写代码如下:

angular.model("ngSlide",[])
.directive("slider",[function(){
	return{
		restrict: 'A',
        replace: true,
		template:"" +
                "<div class='cc tibooslider'>" +
                '<ul class="cc" ></ul>' +
                "</div>"
	}	
}])

这里ngSlide的是你的ng-app名,restrict指的是指令的声明方式,其实A是指属性方式,如果将其定义为E的话会更加语义化,可以这样调用

<slider><slider>

但是因为这种方式对IE6和7不太友好,所以我写成了A,replace则是指是否覆盖你声明指令的代码,template就是你要覆盖的代码了,
拥有了这段代码以后,你只要在你的index.html中写如下代码:

<html ng-app="ngSlide">
	<body  ng-controller="slidecon">
		<div slider>
		</div>
		<script src='angular.js'>
		<script src='ngSlide.js'>
		<script>
			angular.module("ngSlide",[])
	            .controller("slider",function($scope){
	            });
		</script>
	</body>
</html> 

就会自动被浏览器渲染成

<div class="cc" tibooslider>
	<ul class="cc" ></ul>	
</div>

transclude

接下来还要生成那些li,因为图片的地址和上面的文字每次使用插件的时候是不一样的,这是属于“配置”的一部分,这些应该被放在声明里面.将index.html改为

<html ng-app="ngSlide">
	<body  ng-controller="slidecon">
		<div slider>
		    <li>
		            <img src="1.jpg" alt=""/>
		            <p>标题</p>
		    </li>
		    <li>
		        <img src="1.jpg" alt=""/>
		        <p>标题</p>
		    </li>
		    <li>
		        <img src="1.jpg" alt=""/>
		        <p>标题</p>
		    </li>
		</div>
		<script>
			angular.module("ngSlide",[])
	            .controller("slider",function($scope){
	            });
		</script>
	</body>
</html> 

刷新浏览器,...什么都没有发生,因为浏览器并不知道要将这些li插入到模板的什么地方去,这时就需要用到我们的transclude了。将ngSlide.js修改为

angular.model("ngSlide",[])
.directive("slider",[function(){
	return{
		restrict: 'A',
        replace: true,
		transclude:true,
		template:"" +
                "<div class='cc tibooslider'>" +
                '<ul class="cc" ng-transclude></ul>' +
                "</div>"
	}	
}])

这里就很简单明了啦,当浏览器看到transclude声明时就会去寻找模板里带ng-transclude的地方把声明中带的标签包含进去。至此ngSlide的HTML部分就渲染完毕了。

幻灯效果

接下来还需要为ngSlide制作幻灯切换效果,最好的办法是用css动画自己重写一遍,因为angualr的watch机制能让你减少很多代码,不过因为想要快一点搭建完自己的UI库以及兼容IE8所以我选择了我常用的一个jquery幻灯插件SuperSlide。虽然这样会让这部分代码显得有点“脏”(原则上来说angular项目是不推荐引入jquery的)

scope

angular不允许在控制器中操作DOM,而幻灯动画这种事情就是赤裸裸的DOM操作了,它必须写在drective里面,通常来说我调用我的jquery插件时是这样的

$(id).slide({mainCell:ulid});

可以看到我需要2个参数,一个是幻灯最外层div的id,一个是ul的id,而ul是由angular自动生成的,我决定只让用户输入外层id一个参数就可以了,这时需要用到directive的scope,修改ngSlide.js代码如下

angular.model("ngSlide",[])
.directive("slider",[function(){
    return{
        restrict: 'A',
        replace: true,
		scope:{
			id:"@id"
		},
        transclude:true,
        template:"" +
                "<div class='cc tibooslider'>" +
                '<ul class="cc" ng-transclude></ul>' +
                "</div>"
    }   
}])

id:"@id"指的是把指令中的id属性赋值给scope.id,当用户再HTML中输入以下代码时

<div slider id="slideid">

scope.id就会自动被复制为slideid,这时需要的参数都有了,那么原则上来讲我只要在directive里面调用我的jquery插件就可以完成这个伟业了。修改ngSlide.js代码如下:

angular.model("ngSlide",[])
.directive("slider",[function(){
    return{
        restrict: 'A',
        replace: true,
		scope:{
			id:"@id"
		},
        transclude:true,
        template:"" +
                "<div class='cc tibooslider'>" +
                '<ul class="cc" ng-transclude id="{{ulid}}"></ul>' +
                "</div>",
		link:function(elem,scope,attr){
			var id = "#" + scope.id
			var ulid = id + "ul";
			$(id).slide({mainCell:ulid});
		}
    }   
}])

刷新浏览器,运行,。。。。什么变化都没有,这里有一个深坑,原因我们来做一下实验,在link函数里面把elem给输出出来看一下:

console.log(elem[0].innerHTML);

结果如下:

<div class='cc tibooslide' slider id='slideid'>
	<ul class='cc' ng-transclude id="{{ulid}}">	
		<li>
                <img src="1.jpg" alt=""/>
                <p>标题</p>
        </li>
        <li>
            <img src="1.jpg" alt=""/>
            <p>标题</p>
        </li>
        <li>
            <img src="1.jpg" alt=""/>
            <p>标题</p>
        </li>
	</ul>
</div>

注意这个id="{{ulid}}",在这里ul的id数据根本就没有被渲染出来,在angular中model的渲染完成是在directive的link函数执行以后的。这可就愁白了我的头发了,如果使用$timeout的话因为用户的网速不一致我并不知道要延迟多久再加载这段代码,而angular偏偏没有提供directive加载完成的事件,这个问题足足困扰了我3天。

解决方案

这个问题的正解是将jquery插件用一个不设定参数的$timeout给包裹起来,这样就一定会在id值被渲染出来以后才执行它,造成这种情况的原因是js是阻塞运行的,$timeout只会在空闲时才会执行里面的代码,修改ngSlide代码如下:

angular.model("ngSlide",[])
.directive("slider",['$timeout',function($timeout){
	//别忘了依赖注入
    return{
        restrict: 'A',
        replace: true,
		scope:{
			id:"@id"
		},
        transclude:true,
        template:"" +
                "<div class='cc tibooslider'>" +
                '<ul class="cc" ng-transclude id="{{ulid}}"></ul>' +
                "</div>",
		link:function(elem,scope,attr){
			$timeout(function(){
				var id = "#" + scope.id
				var ulid = id + "ul";
				$(id).slide({mainCell:ulid});
			})
			
		}
    }   
}])

至此程序就能正常运行了,当然接下来还要做少许的配置,例如说插件需要向左向右的箭头该怎么办呀,这部分的代码大家可以移步我的github查看,https://github.com/RenShine/ngSlide。下一篇文章我将会介绍这个插件的SCSS部分是如何实现的

angularJS指令实战,制作一个可复用的幻灯组件(一)

posted @ 2014-10-15 13:53  RenShine  阅读(1772)  评论(0编辑  收藏  举报