Documentation : Relations - iOS

This example shows how to work with models relations using Mobeelizer.

Application consists of two models. First one Order has two fields - name and status, secone one is an order item which contains title field and field definig relation between models. Main screen of application contains list of model entities and two buttons. User can add new order and it's items. He can also synchronize with mobeelizer cloud. There are two users in the system and there's a possibility to switch user on application life time.

Design Your App

To complete this example we need to create new application called Relations in Mobeelizer App Designer.

Next we will configure model. We have to go to 'Models' section and create:

  • OrderEntity (CRUD operations avaliable for everyone) with the following fields:

    Name
    Required property
    Type
    Default value property
    Additional properties
    Credentials
    nameyestext--default
    statusyesinteger1min: 1, max: 5default

     

  • OrderItemEntity (CRUD operations avaliable for everyone) with the following fields:

    Name
    Required property
    Type
    Default value property
    Additional properties
    Credentials
    titleyestext--default
    orderGuidyesbelongs to OrderEntity--default


For this example we leave conflict resolving setting in overwrite state. 

Next section to configure is 'Groups & Roles', by default there is one group called 'users' and one device category 'mobile' and this two together create role 'users-mobile'. This default configuration is perfect for our example and you don't have to change it. 
When everything is done in Create mode, deploy our application to test environment, and create two users with passwords:

  • a - usera
  • b - userb

Finally, download configured template for iOS project. Change class prefix to MD and model prefix to MMD.

Use the Mobeelizer SDK

Open project in XCode. We can see that several files has been generated and configured.  

Configure AppDelegate

First of all, there is app delegate with configured creation and destruction of Mobeelizer object. Generated application does not have any view. We will add storyboard: MDStoryboad to our application and set newly created storyboard as Main Storyboard in project settings. Furthermore, we have to remove setting new window from application: didFinishLaunchingWithOptions: in MDAppDelegate

MDAppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Initialize Mobeelizer SDK
    [Mobeelizer create];
 
    return YES;
}
- (void)applicationWillTerminate:(UIApplication *)application
{   
    // Terminate Mobeelizer SDK
    [Mobeelizer destroy];
}

MDAppDelegate.m

Configure Mobeelizer entity

Both application.xml and Mobeelizer.plist were generated. In Mobeelizer.plist we have to change mode from 'development' to 'production'. Moreover, model has been generated for OrderEntity and OrderItemEntity. We will add guid and owner  fields together with custom costructor. We will also overwrite isEqual function

MMDOrderEntity.h
@property(strong, nonatomic) NSString* guid;
@property(strong, nonatomic) NSString* owner;
@property(strong, nonatomic) NSString* name;
@property(strong, nonatomic) NSNumber* status;

- (MMDOrderEntity*) initWithName:(NSString*)name andStatus:(NSUInteger)status;
MMDOrderEntity.m
- (MMDOrderEntity*) initWithName:(NSString*)name andStatus:(NSUInteger)status {
    self.name = name;
    self.status = [NSNumber numberWithInt:status];
    return self;
}

- (BOOL)isEqual:(id)object {
    if ([object isKindOfClass:[MMDOrderEntity class]]) {
        MMDOrderEntity* entity = object;
        if ([entity.guid isEqualToString:self.guid] && [entity.name isEqualToString:self.name] && entity.status == self.status) {
            return YES;
        }
    }
    return NO;
}

MMDOrderEntity.h MMDOrderEntity.m

MMDOrderItemEntity.h
@property(strong, nonatomic) NSString* guid;
@property(strong, nonatomic) NSString* owner;
@property(strong, nonatomic) NSString* orderGuid;
@property(strong, nonatomic) NSString* title;

- (MMDOrderItemEntity*) initWithTitle:(NSString*)title andOrder:(NSString*)orderGuid;
MMDOrderItemEntity.m
- (MMDOrderItemEntity*) initWithTitle:(NSString*)title andOrder:(NSString*)orderGuid {
    self.title = title;
    self.orderGuid = orderGuid;
    return self;
}

- (BOOL)isEqual:(id)object {
    if ([object isKindOfClass:[MMDOrderItemEntity class]]) {
        MMDOrderItemEntity* entity = object;
        if ([entity.guid isEqualToString:self.guid] && [entity.title isEqualToString:self.title]) {
            return YES;
        }
    }
    return NO;
}

MMDOrderItemEntity.h MMDOrderItemEntity.m

Create user login and logout manager

Next, we will handle user functionality. We will create singleton class MDUserManager, which will hold information about currently logged user. There are two use cases to handle user functionality. Firstly, when application starts user a will be logged in. Secondly, during runtime, users can be switched.

MDUserManager.h
@property (copy, nonatomic) NSString* user;
 
+ (SSUserManager*)instance;
- (BOOL) performLoginAsUser:(NSString *)login;
- (void) switchUser;
MDUserManager.m
- (BOOL) performLoginAsUser:(NSString *)login{
    self.user = login;
    NSString* password = nil;
    if ([login isEqualToString:USER_A]) {
        password = @"usera";
    } else {
        password = @"userb";
    }
 	//Login to Mobeelizer instance
    MobeelizerOperationError *error = [Mobeelizer loginToInstance:@"test" withUser:login andPassword:password];
 
    if(error != nil) {
        NSLog(@"Joining failed: %@ - %@", error.code, error.message);
    }
    return error == nil;
}
 
-(void) switchUser{
    if ([self.user isEqualToString:USER_A]) {
        self.user = USER_B;
    } else {
        self.user = USER_A;
    }
 
    [self performLogout];
    [self performLoginAsUser:self.user];
}
 
- (void) performLogout {
	//Logout from Mobeelizer
    [Mobeelizer logout];
}

MDUserManager.h MDUserManager.m

Next, we will move to user interface. Add a new file to project. Choose the UITableViewController subclass template and name the class MDUserContextController. We will add button to navigation bar, which will handle switching users. Controller will hold reference to button, because we will change appearance of button to show currently logged user.

MDUserContextController.h
#define RGB(r, g, b) [UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:1]
#define USER_A_COLOR RGB(255, 128, 0)
#define USER_B_COLOR RGB(0, 128, 0)
 
@property (strong, nonatomic) UIBarButtonItem *userButton;
MDUserContextController.m
- (void)viewDidLoad{
    [super viewDidLoad];
    [[self navigationController] setNavigationBarHidden:NO animated:NO];
    userButton = [[UIBarButtonItem alloc] initWithTitle:@"User" style:UIBarButtonItemStyleBordered target:self action:@selector(userButtonClicked:)];
    self.navigationItem.rightBarButtonItem = userButton;
 
    [[MDUserManager instance] performLoginAsUser:USER_A];
}

Now add function to handle button click:

MDUserContextController.m
- (IBAction)userButtonClicked:(id)sender {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [[MDUserManager instance] switchUser];
 
        dispatch_async(dispatch_get_main_queue(), ^{
            [self refreshUserButton];
 
            [UIView beginAnimations:@"View Flip" context:nil];
            [UIView setAnimationDuration:0.75];
            [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
            [UIView setAnimationTransition: UIViewAnimationTransitionFlipFromRight forView:self.view cache:YES];
            [self reloadData];
            [UIView commitAnimations];
        });
    });
}
 
- (void)refreshUserButton {
    if ([[MDUserManager instance].user isEqualToString:USER_B]) {
        [userButton setTitle:@"B"];
        [userButton setTintColor:USER_B_COLOR];
    } else {
        [userButton setTitle:@"A"];
        [userButton setTintColor:USER_A_COLOR];
    }
}

MDUserContextController.h MDUserContextController.m

Currently, if any controller extends MDUserContextController, it will have functionality of switching users.

Create entity table view controller

Now, we will create view. Add navigation controller with table view controller to storyboard. Select table view and in Attributes Inspector change numer of prototypes cell to 3. First prototype cell will present order with appropriate status. Second cell will present order's item. Last prototype cell will allow adding new positions to specific order. Choose first prototype cell. Set prototype cell identifier to RelationsOrderSyncCell and set style to custom. Then set accessory to disclosure indicator. Add label to prototype cell, change its name to Order name and set tag value to 1. Then add image view  and set tag value to 2. Image view will be placeholder for icon with order status. Now, select second prototype cell and set it's identifier to RelationsItemSyncCell. Add label Position and set tag value to 1. Finally, pick last prototype cell and set identifier to RelationsAddItemCell. Add label Add position and button with type Add contact. Afterwards, add a new file to the project. Choose the MDUserContextController subclass template and name the class MDRelationsController. Connect newly created controller to its design in storyboard. MDRelationsController will hold an array of table records. Moreover, we will mark added records with appropriate user image. Orders will be marked with status icon. 

MDRelationsController.h
@property(strong, nonatomic) NSMutableArray *currentItems;
@property(strong, nonatomic) UIImage *userAImage;
@property(strong, nonatomic) UIImage *userBImage;
@property(strong, nonatomic) NSArray *statusImages;
MDRelationsController.m
- (void)viewDidLoad {
    [super viewDidLoad];
    [self addToolbarButtons];

    NSMutableArray* images = [[NSMutableArray alloc] init];
    for (int i=0; i<5; i++) {
        NSString* imageName = [NSString stringWithFormat:@"status_%d.png", (i+1)];
        [images addObject:[UIImage imageNamed:imageName]];
    }
    statusImages = [[NSArray alloc] initWithArray:images];
    
    self.currentItems = [[NSMutableArray alloc] initWithArray:[self getItemsList]];
    userAImage = [UIImage imageNamed:@"userA.png"];
    userBImage = [UIImage imageNamed:@"userB.png"];
}
-(void)addToolbarButtons{
    UIBarButtonItem* newButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(newClicked:)];
    [newButton setStyle:UIBarButtonItemStyleBordered];
    UIBarButtonItem *space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
    [space setWidth:10];
    UIBarButtonItem* syncButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(syncClicked:)];
    [syncButton setStyle:UIBarButtonItemStyleBordered];

    self.toolbarItems = @[newButton, space, syncButton];
    self.navigationController.toolbarHidden = NO;
}

In this example we have three types of objects that can be contained in table. First of all, we have orders, then we have positions and finally add item instances. Therefore, we will introduce new class to wrap all possible objects. Item can be one of three possible types, so we will create enum:

MDRelationsItem.h
typedef enum {
    MDRelationsItemTypeOrder = 0,
    MDRelationsItemTypeItem,
    MDRelationsItemTypeAddItem,
} MDRelationsItemType;

Object, beside item type, will be holding entity and its identifier. We will also introduce custom initialization for each type of object.

MDRelationsItem.h
@property(nonatomic) MDRelationsItemType type;
@property(nonatomic, strong) id entity;
@property(nonatomic, copy) NSString* guid;

- (MDRelationsItem*)initAsOrdertWithEntity:(MMDOrderEntity*)entity;
- (MDRelationsItem*)initAsItemWithEntity:(MMDOrderItemEntity*)entity;
- (MDRelationsItem*)initAsAddItemWithOrderEntity:(MMDOrderEntity*)entity;
MDRelationsItem.m
- (MDRelationsItem*)initAsOrdertWithEntity:(MMDOrderEntity*)entity {
    self.type = MDRelationsItemTypeOrder;
    self.entity = entity;
    self.guid = entity.guid;
    return self;
}

- (MDRelationsItem*)initAsItemWithEntity:(MMDOrderItemEntity*)entity {
    self.type = MDRelationsItemTypeItem;
    self.entity = entity;
    self.guid = entity.guid;
    return self;
}

- (MDRelationsItem*)initAsAddItemWithOrderEntity:(MMDOrderEntity*)entity {
    self.type = MDRelationsItemTypeAddItem;
    self.entity = entity;
    self.guid = [NSString stringWithFormat:@"add_to_%@", entity.guid];
    return self;
}

MDRelationsItem.h MDRelationsItem.m

Current items array is intialized with MDRelationsItem. First of all, all orders are taken from Mobeelizer database. Then for each order, all related items are taken. Finally, add item is added for each order. Each instance is wrapped with MDRelationsItem class.

MDRelationsController.m
- (NSArray*)getItemsList {
	//Get all MMDOrderEntity and MMDOrderItemEntity from Mobeelizer database
    NSMutableArray* result = [[NSMutableArray alloc] init];
    MobeelizerCriteriaBuilder *ordersCriteria = [[Mobeelizer database] find:[MMDOrderEntity class]];
    ordersCriteria = [ordersCriteria addOrder:[MobeelizerOrder asc:@"name"]];

    for (MMDOrderEntity* order in [ordersCriteria list]) {
        [result addObject:[[MDRelationsItem alloc] initAsOrdertWithEntity:order]];
        MobeelizerCriteriaBuilder *itemsCriteria = [[Mobeelizer database] find:[MMDOrderItemEntity class]];
        itemsCriteria = [itemsCriteria add:[MobeelizerCriterion field:@"orderGuid" eq:order.guid]];
        itemsCriteria = [itemsCriteria addOrder:[MobeelizerOrder asc:@"title"]];

        for (MMDOrderItemEntity* item in [itemsCriteria list]) {
            [result addObject:[[MDRelationsItem alloc] initAsItemWithEntity:item]];
        }
        [result addObject:[[MDRelationsItem alloc] initAsAddItemWithOrderEntity:order]];
    }
    return result;
}

Orders are name with following format Order (username)/(user order).Therefore, in order to create new order we count all orders of logged user in Mobeelizer database and increament by one.

MDRelationsController.m
- (void)newClicked:(id)sender {
	//Count all MMDOrderEntity for particular user in Mobeelizer database
    MobeelizerCriteriaBuilder *ordersCriteria = [[Mobeelizer database] find:[MMDOrderEntity class]];
    ordersCriteria = [ordersCriteria add:[MobeelizerCriterion ownerEq:[[MDUserManager instance] user]]];
    NSString* orderName = [NSString stringWithFormat:@"%@/%03d", [[MDUserManager instance] user], ([ordersCriteria count] + 1)];
    MMDOrderEntity* order = [[MMDOrderEntity alloc] initWithName:orderName andStatus:1];
	
	//Save new MMDOrderEntity in Mobeelizer database    
	[[Mobeelizer database] save:order];
    [self reloadData];
}
-(void)reloadData{
    currentItems = [[NSMutableArray alloc] initWithArray:[self getItemsList]];
    [self.tableView reloadData];
}

However, adding item to an order is completely different. Selecting Add position cell will add item to appropriate order.

MDRelationsController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    MDRelationsItem* item = currentItems[indexPath.row];
    if (item.type == MDRelationsItemTypeAddItem) {
        UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
        [cell setSelected:NO animated:YES];
        MDMovie* movie = [[MDMovies instance] getRandomMovie];
        MMDOrderItemEntity* newItem = [[MMDOrderItemEntity alloc] initWithTitle:movie.title andOrder:[item.entity guid]];
        
		//Save new MMDOrderItemEntity in Mobeelizer database
		[[Mobeelizer database] save:newItem];
        [self reloadData];
    }
}

As positions we are using random movies titles. We will introduce two objects MDMovie and MDMovies. MDMovie is simple object consisting of title and director property. 

MDMovie.h
@property(copy, nonatomic) NSString* title;
@property(copy, nonatomic) NSString* director;

-(MDMovie*) initWithDictionary:(NSDictionary*)dictionary;

 

MDMovie.m
-(MDMovie*) initWithDictionary:(NSDictionary*)dictionary {
    self.title = [dictionary valueForKey:@"title"];
    self.director = [dictionary valueForKey:@"director"];
    return self;
}

 MDMovie.h MDMovie.m

 MDMovies is singleton, which generates random movie from sample plist. 

MDMovies.m
- (id)init {
    if (movies == nil) {
        self = [super init];
        if (self) {
            NSString *path = [[NSBundle mainBundle] pathForResource:@"Movies" ofType:@"plist"];
            movies = [[NSArray alloc] initWithContentsOfFile:path];
        }
    }
    return self;
}

- (MDMovie*)getRandomMovie {
    int randomIndex = arc4random() % [movies count];
    return [[MDMovie alloc] initWithDictionary:movies[randomIndex]];
} 

MDMovies.h MDMovies.m

Second button in toolbar is responsible for synchronization. After synchronization finishes we have to reload data in table.

 

MDRelationsController.m
- (void)syncClicked:(id) sender {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
		//Synchronize with Mobeelizer cloud
        MobeelizerOperationError *error = [Mobeelizer sync];
        if(error != nil) {
            NSLog(@"Sync failure: %@ - %@", error.code, error.message);
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            [self reloadData];
        });
    });
}
-(void)reloadData{
    currentItems = [[NSMutableArray alloc] initWithArray:[self getItemsList]];
    [self.tableView reloadData];
}

Now we will configure our table view controller.

MDRelationsController.m
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return currentItems.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    MDRelationsItem* item = currentItems[[indexPath row]];
    UITableViewCell* cell = [self createCellForItem:item atRow:indexPath.row];
    if (item.type == MDRelationsItemTypeOrder || item.type == MDRelationsItemTypeItem) {
        UIImageView* userLabel = (UIImageView*)[cell.contentView viewWithTag:1001];
        CGFloat userLabelY = (cell.contentView.frame.size.height - 30) /2; // 7
        CGFloat userLabelX = (cell.contentView.frame.size.width - 37); // 283
        if (userLabel == nil) {
            CGRect userLabelRect = CGRectMake(userLabelX, userLabelY, 30, 30);
            userLabel = [[UIImageView alloc] initWithFrame:userLabelRect];
            [userLabel setTag:1001];
            [cell.contentView addSubview:userLabel];
        }
        if ([[item.entity owner] isEqual:USER_A]) {
            userLabel.image = userAImage;
        } else {
            userLabel.image = userBImage;
        }
    }
    return cell;
}

- (UITableViewCell*)createCellForItem:(MDRelationsItem*)item atRow:(NSInteger)row {
    static NSString *OrderIdentifier = @"RelationsOrderSyncCell";
    static NSString *ItemIdentifier = @"RelationsItemSyncCell";
    static NSString *AddItemIdentifier = @"RelationsAddItemCell";

    UITableViewCell *cell = nil;
    MMDOrderEntity* orderEntity = nil;
    MMDOrderItemEntity* itemEntity = nil;
    UILabel* nameLabel = nil;
    UIImageView* imageView = nil;
    switch (item.type) {
        case MDRelationsItemTypeOrder:
            cell = [self.tableView dequeueReusableCellWithIdentifier:OrderIdentifier];
            orderEntity = (MMDOrderEntity*) item.entity;
            nameLabel = (UILabel*)[cell viewWithTag:1];
            nameLabel.text = [NSString stringWithFormat:@"Order %@", orderEntity.name];
            imageView = (UIImageView*)[cell viewWithTag:2];
            imageView.image = statusImages[([orderEntity.status unsignedIntegerValue] - 1)];
            break;
        case MDRelationsItemTypeItem:
            cell = [self.tableView dequeueReusableCellWithIdentifier:ItemIdentifier];
            itemEntity = (MMDOrderItemEntity*) item.entity;
            nameLabel = (UILabel*)[cell viewWithTag:1];
            nameLabel.text = itemEntity.title;
            break;
        case MDRelationsItemTypeAddItem:
            cell = [self.tableView dequeueReusableCellWithIdentifier:AddItemIdentifier];
            break;
    }
    return cell;
}

Create entity detail page

Finally, we will introduce possibility to change order status. Add another table view controller to MDStoryboard. Connect order cell from MDRelationsController to newly created view and from selection segue pick push option.  This will enable transition from selected record to detail view. Now select new table view and open Attributes Inspector. Change content setting from dynamic prototypes to static cells. Now select table view section and, also in Attributes Inspector, change number of rows to 5 and header to Status. For each cell select Basic style and add following title and image:

  1. New - status_1.png
  2. Pending - status_2.png
  3. Ready to ship - status_3.png
  4. Shipped - status_4.png
  5. Recieved - status_5.png

Then, add a new file to the project. Choose UITableViewController subclass template and name the class MDRelationsOrderDetailController. Connect newly created controller to its design in storyboard.  We need selected entity and row number. Moreover, we need delegate to MDRelationsController in order to update modified entity. 

MDRelationsOrderDetailController.h
@property (nonatomic) NSUInteger row;
@property (strong, nonatomic) MDRelationsController* delegate;
@property (strong, nonatomic) MMDOrderEntity* entity;

After selecting proper status, we update entity in Mobeelizer database and in MDRelationsController.

MDRelationsOrderDetailController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell* oldCell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:[self.entity.status intValue]-1 inSection:0]];
    oldCell.accessoryType = UITableViewCellAccessoryNone;

    self.entity.status = [NSNumber numberWithInt:indexPath.row + 1];
	//Update entity in Mobeelizer database
    [[Mobeelizer database] save:self.entity];
    MDRelationsItem* newOrderItem = [[MDRelationsItem alloc] initAsOrdertWithEntity:self.entity];
    [self.delegate updateRow:self.row withItem:newOrderItem];

    UITableViewCell* newCell = [self.tableView cellForRowAtIndexPath:indexPath];
    newCell.accessoryType = UITableViewCellAccessoryCheckmark;

    [[self navigationController] popViewControllerAnimated:YES];
}

MDRelationsOrderDetailController.h MDRelationsOrderDetailController.m

Last, but not the least we have to implement update record and transition between MDRelationsController and MDRelationsOrderDetailController. Since we are using storyboard, we have to implement prepareForSegue:sender:

MDRelationsController.m
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"goToRelationsOrderDetail"]) {
        MDRelationsOrderDetailController *dc = [segue destinationViewController];
        NSUInteger row = [self.tableView indexPathForSelectedRow].row;
        dc.delegate = self;
        dc.row =  row;
        dc.entity = [currentItems[row] entity];
    } else if ([segue.identifier isEqualToString:@"goToRelationsItemDetail"]){
        MDRelationsOrderDetailController *dc = [segue destinationViewController];
        NSUInteger row = [self.tableView indexPathForSelectedRow].row;
        dc.delegate = self;
        dc.row =  row;
        dc.entity = [currentItems[row] entity];
    }
    [super prepareForSegue:segue sender:sender];
}
- (void)updateRow:(NSInteger)row withItem:(id)item {
    NSIndexPath* indexPath = [NSIndexPath indexPathForRow:row inSection:0];
    if (item == nil) {
        [currentItems removeObjectAtIndex:row];
        [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
    } else {
        currentItems[row] = item;
        [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
    }
}

MDRelationsController.h MDRelationsController.m

There are few files, which are necessary Movies.plistuserA.pnguserB.png.

Conclusion

Congratulations, relation example is completed. You can test it now, you can also add your own code to it, for example you can implement removing order items or orders.

To conclude, this is the simple example of using relations in Mobeelizer. We have two models connected with 'belongs to' field. We can create orders, add items to them and synchronize with Mobeelizer cloud.

Attachments:

MMDOrderEntity.h (application/octet-stream)
MMDOrderEntity.m (application/octet-stream)
MMDOrderItemEntity.h (application/octet-stream)
MMDOrderItemEntity.m (application/octet-stream)
MDRelationsItem.h (application/octet-stream)
MDRelationsItem.m (application/octet-stream)
status_1.png (image/png)
status_2.png (image/png)
status_3.png (image/png)
status_4.png (image/png)
status_5.png (image/png)
MDRelationsOrderDetailController.h (application/octet-stream)
MDRelationsOrderDetailController.m (application/octet-stream)
MDRelationsController.h (application/octet-stream)
MDRelationsController.m (application/octet-stream)
Relations.png (image/png)
OrderEntity.png (image/png)
NameField.png (image/png)
StatusField.png (image/png)
OrderItemEntity.png (image/png)
TitleFieldReq.png (image/png)
OrderGuid.png (image/png)
AddUser.png (image/png)
RelationsTemplate.png (image/png)